redis-3.2.2/0000755000004100000410000000000012637240307012660 5ustar www-datawww-dataredis-3.2.2/Rakefile0000644000004100000410000000370512637240307014332 0ustar www-datawww-datarequire "rake/testtask" ENV["REDIS_BRANCH"] ||= "unstable" REDIS_DIR = File.expand_path(File.join("..", "test"), __FILE__) REDIS_CNF = File.join(REDIS_DIR, "test.conf") REDIS_CNF_TEMPLATE = File.join(REDIS_DIR, "test.conf.erb") REDIS_PID = File.join(REDIS_DIR, "db", "redis.pid") REDIS_LOG = File.join(REDIS_DIR, "db", "redis.log") REDIS_SOCKET = File.join(REDIS_DIR, "db", "redis.sock") BINARY = "tmp/redis-#{ENV["REDIS_BRANCH"]}/src/redis-server" task :default => :run desc "Run tests and manage server start/stop" task :run => [:start, :test, :stop] desc "Start the Redis server" task :start => [BINARY, REDIS_CNF] do sh "#{BINARY} --version" redis_running = \ begin File.exists?(REDIS_PID) && Process.kill(0, File.read(REDIS_PID).to_i) rescue Errno::ESRCH FileUtils.rm REDIS_PID false end unless redis_running unless system("#{BINARY} #{REDIS_CNF}") abort "could not start redis-server" end end at_exit do Rake::Task["stop"].invoke end end desc "Stop the Redis server" task :stop do if File.exists?(REDIS_PID) Process.kill "INT", File.read(REDIS_PID).to_i FileUtils.rm REDIS_PID end end desc "Clean up testing artifacts" task :clean do FileUtils.rm_f(BINARY) FileUtils.rm_f(REDIS_CNF) end file BINARY do branch = ENV.fetch("REDIS_BRANCH") sh <<-SH mkdir -p tmp; cd tmp; rm -rf redis-#{branch}; wget https://github.com/antirez/redis/archive/#{branch}.tar.gz -O #{branch}.tar.gz; tar xf #{branch}.tar.gz; cd redis-#{branch}; make SH end file REDIS_CNF => [REDIS_CNF_TEMPLATE, __FILE__] do |t| require 'erb' erb = t.prerequisites[0] template = File.read(erb) File.open(REDIS_CNF, 'w') do |file| file.puts "\# This file was auto-generated at #{Time.now}", "\# from (#{erb})", "\#" conf = ERB.new(template).result file << conf end end Rake::TestTask.new do |t| t.options = "-v" if $VERBOSE t.test_files = FileList["test/*_test.rb"] end redis-3.2.2/benchmarking/0000755000004100000410000000000012637240307015310 5ustar www-datawww-dataredis-3.2.2/benchmarking/pipeline.rb0000644000004100000410000000155512637240307017450 0ustar www-datawww-datarequire "benchmark" $:.push File.join(File.dirname(__FILE__), 'lib') require 'redis' ITERATIONS = 10000 @r = Redis.new Benchmark.bmbm do |benchmark| benchmark.report("set") do @r.flushdb ITERATIONS.times do |i| @r.set("foo#{i}", "Hello world!") @r.get("foo#{i}") end end benchmark.report("set (pipelined)") do @r.flushdb @r.pipelined do ITERATIONS.times do |i| @r.set("foo#{i}", "Hello world!") @r.get("foo#{i}") end end end benchmark.report("lpush+ltrim") do @r.flushdb ITERATIONS.times do |i| @r.lpush "lpush#{i}", i @r.ltrim "ltrim#{i}", 0, 30 end end benchmark.report("lpush+ltrim (pipelined)") do @r.flushdb @r.pipelined do ITERATIONS.times do |i| @r.lpush "lpush#{i}", i @r.ltrim "ltrim#{i}", 0, 30 end end end end redis-3.2.2/benchmarking/logging.rb0000644000004100000410000000241512637240307017265 0ustar www-datawww-data# Run with # # $ ruby -Ilib benchmarking/logging.rb # begin require "bench" rescue LoadError $stderr.puts "`gem install bench` and try again." exit 1 end require "redis" require "logger" def log(level, namespace = nil) logger = (namespace || Kernel).const_get(:Logger).new("/dev/null") logger.level = (namespace || Logger).const_get(level) logger end def stress(redis) redis.flushdb n = (ARGV.shift || 2000).to_i n.times do |i| key = "foo:#{i}" redis.set key, i redis.get key end end default = Redis.new logging_redises = [ Redis.new(:logger => log(:DEBUG)), Redis.new(:logger => log(:INFO)), ] begin require "log4r" logging_redises += [ Redis.new(:logger => log(:DEBUG, Log4r)), Redis.new(:logger => log(:INFO, Log4r)), ] rescue LoadError $stderr.puts "Log4r not installed. `gem install log4r` if you want to compare it against Ruby's Logger (spoiler: it's much faster)." end benchmark "Default options (no logger)" do stress(default) end logging_redises.each do |redis| logger = redis.client.logger case logger when Logger level = Logger::SEV_LABEL[logger.level] when Log4r::Logger level = logger.levels[logger.level] end benchmark "#{logger.class} on #{level}" do stress(redis) end end run 10 redis-3.2.2/benchmarking/speed.rb0000644000004100000410000000047212637240307016740 0ustar www-datawww-data# Run with # # $ ruby -Ilib benchmarking/speed.rb # require "benchmark" require "redis" r = Redis.new n = (ARGV.shift || 20000).to_i elapsed = Benchmark.realtime do # n sets, n gets n.times do |i| key = "foo#{i}" r[key] = key * 10 r[key] end end puts '%.2f Kops' % (2 * n / 1000 / elapsed) redis-3.2.2/benchmarking/worker.rb0000644000004100000410000000267612637240307017161 0ustar www-datawww-dataBENCHMARK_ROOT = File.dirname(__FILE__) REDIS_ROOT = File.join(BENCHMARK_ROOT, "..", "lib") $: << REDIS_ROOT require 'redis' require 'benchmark' def show_usage puts <<-EOL Usage: worker.rb [read:write] EOL end def shift_from_argv value = ARGV.shift unless value show_usage exit -1 end value end operation = shift_from_argv.to_sym start_index = shift_from_argv.to_i end_index = shift_from_argv.to_i sleep_msec = shift_from_argv.to_i sleep_duration = sleep_msec/1000.0 redis = Redis.new case operation when :initialize start_index.upto(end_index) do |i| redis[i] = 0 end when :clear start_index.upto(end_index) do |i| redis.delete(i) end when :read, :write puts "Starting to #{operation} at segment #{end_index + 1}" loop do t1 = Time.now start_index.upto(end_index) do |i| case operation when :read redis.get(i) when :write redis.incr(i) else raise "Unknown operation: #{operation}" end sleep sleep_duration end t2 = Time.now requests_processed = end_index - start_index time = t2 - t1 puts "#{t2.strftime("%H:%M")} [segment #{end_index + 1}] : Processed #{requests_processed} requests in #{time} seconds - #{(requests_processed/time).round} requests/sec" end else raise "Unknown operation: #{operation}" end redis-3.2.2/benchmarking/suite.rb0000644000004100000410000000126312637240307016770 0ustar www-datawww-datarequire 'fileutils' def run_in_background(command) fork { system command } end def with_all_segments(&block) 0.upto(9) do |segment_number| block_size = 100000 start_index = segment_number * block_size end_index = start_index + block_size - 1 block.call(start_index, end_index) end end #with_all_segments do |start_index, end_index| # puts "Initializing keys from #{start_index} to #{end_index}" # system "ruby worker.rb initialize #{start_index} #{end_index} 0" #end with_all_segments do |start_index, end_index| run_in_background "ruby worker.rb write #{start_index} #{end_index} 10" run_in_background "ruby worker.rb read #{start_index} #{end_index} 1" end redis-3.2.2/Gemfile0000644000004100000410000000007112637240307014151 0ustar www-datawww-data# encoding: utf-8 source 'https://rubygems.org' gemspec redis-3.2.2/examples/0000755000004100000410000000000012637240307014476 5ustar www-datawww-dataredis-3.2.2/examples/incr-decr.rb0000644000004100000410000000030612637240307016670 0ustar www-datawww-datarequire 'redis' r = Redis.new puts p 'incr' r.del 'counter' p r.incr('counter') p r.incr('counter') p r.incr('counter') puts p 'decr' p r.decr('counter') p r.decr('counter') p r.decr('counter') redis-3.2.2/examples/sentinel/0000755000004100000410000000000012637240307016317 5ustar www-datawww-dataredis-3.2.2/examples/sentinel/start0000755000004100000410000000256412637240307017411 0ustar www-datawww-data#! /usr/bin/env ruby # This is a helper script used together with examples/sentinel.rb # It runs two Redis masters, two slaves for each of them, and two sentinels. # After 30 seconds, the first master dies. # # You don't need to run this script yourself. Rather, use examples/sentinel.rb. require "fileutils" $pids = [] at_exit do $pids.each do |pid| begin Process.kill(:INT, pid) rescue Errno::ESRCH end end Process.waitall end base = File.expand_path(File.dirname(__FILE__)) # Masters $pids << spawn("redis-server --port 6380 --loglevel warning") $pids << spawn("redis-server --port 6381 --loglevel warning") # Slaves of Master 1 $pids << spawn("redis-server --port 63800 --slaveof 127.0.0.1 6380 --loglevel warning") $pids << spawn("redis-server --port 63801 --slaveof 127.0.0.1 6380 --loglevel warning") # Slaves of Master 2 $pids << spawn("redis-server --port 63810 --slaveof 127.0.0.1 6381 --loglevel warning") $pids << spawn("redis-server --port 63811 --slaveof 127.0.0.1 6381 --loglevel warning") FileUtils.cp(File.join(base, "sentinel.conf"), "tmp/sentinel1.conf") FileUtils.cp(File.join(base, "sentinel.conf"), "tmp/sentinel2.conf") # Sentinels $pids << spawn("redis-server tmp/sentinel1.conf --sentinel --port 26379") $pids << spawn("redis-server tmp/sentinel2.conf --sentinel --port 26380") sleep 30 Process.kill(:KILL, $pids[0]) Process.waitall redis-3.2.2/examples/sentinel/sentinel.conf0000644000004100000410000000050512637240307021007 0ustar www-datawww-datasentinel monitor master1 127.0.0.1 6380 2 sentinel down-after-milliseconds master1 5000 sentinel failover-timeout master1 15000 sentinel parallel-syncs master1 1 sentinel monitor master2 127.0.0.1 6381 2 sentinel down-after-milliseconds master2 5000 sentinel failover-timeout master2 15000 sentinel parallel-syncs master2 1 redis-3.2.2/examples/sentinel.rb0000644000004100000410000000177712637240307016660 0ustar www-datawww-datarequire 'redis' # This example creates a master-slave setup with a sentinel, then connects to # it and sends write commands in a loop. # # After 30 seconds, the master dies. You will be able to see how a new master # is elected and things continue to work as if nothing happened. # # To run this example: # # $ ruby -I./lib examples/sentinel.rb # at_exit do begin Process.kill(:INT, $redises) rescue Errno::ESRCH end Process.waitall end $redises = spawn("examples/sentinel/start") Sentinels = [{:host => "127.0.0.1", :port => 26379}, {:host => "127.0.0.1", :port => 26380}] r = Redis.new(:url => "redis://master1", :sentinels => Sentinels, :role => :master) # Set keys into a loop. # # The example traps errors so that you can actually try to failover while # running the script to see redis-rb reconfiguring. (0..1000000).each{|i| begin r.set(i,i) $stdout.write("SET (#{i} times)\n") if i % 100 == 0 rescue => e $stdout.write("E") end sleep(0.01) } redis-3.2.2/examples/dist_redis.rb0000644000004100000410000000145112637240307017155 0ustar www-datawww-datarequire "redis" require "redis/distributed" r = Redis::Distributed.new %w[redis://localhost:6379 redis://localhost:6380 redis://localhost:6381 redis://localhost:6382] r.flushdb r['urmom'] = 'urmom' r['urdad'] = 'urdad' r['urmom1'] = 'urmom1' r['urdad1'] = 'urdad1' r['urmom2'] = 'urmom2' r['urdad2'] = 'urdad2' r['urmom3'] = 'urmom3' r['urdad3'] = 'urdad3' p r['urmom'] p r['urdad'] p r['urmom1'] p r['urdad1'] p r['urmom2'] p r['urdad2'] p r['urmom3'] p r['urdad3'] r.rpush 'listor', 'foo1' r.rpush 'listor', 'foo2' r.rpush 'listor', 'foo3' r.rpush 'listor', 'foo4' r.rpush 'listor', 'foo5' p r.rpop('listor') p r.rpop('listor') p r.rpop('listor') p r.rpop('listor') p r.rpop('listor') puts "key distribution:" r.ring.nodes.each do |node| p [node.client, node.keys("*")] end r.flushdb p r.keys('*') redis-3.2.2/examples/pubsub.rb0000644000004100000410000000146612637240307016332 0ustar www-datawww-datarequire "redis" puts <<-EOS To play with this example use redis-cli from another terminal, like this: $ redis-cli publish one hello Finally force the example to exit sending the 'exit' message with: $ redis-cli publish two exit EOS redis = Redis.new trap(:INT) { puts; exit } begin redis.subscribe(:one, :two) do |on| on.subscribe do |channel, subscriptions| puts "Subscribed to ##{channel} (#{subscriptions} subscriptions)" end on.message do |channel, message| puts "##{channel}: #{message}" redis.unsubscribe if message == "exit" end on.unsubscribe do |channel, subscriptions| puts "Unsubscribed from ##{channel} (#{subscriptions} subscriptions)" end end rescue Redis::BaseConnectionError => error puts "#{error}, retrying in 1s" sleep 1 retry end redis-3.2.2/examples/consistency.rb0000644000004100000410000001002412637240307017361 0ustar www-datawww-data# This file implements a simple consistency test for Redis-rb (or any other # Redis environment if you pass a different client object) where a client # writes to the database using INCR in order to increment keys, but actively # remember the value the key should have. Before every write a read is performed # to check if the value in the database matches the value expected. # # In this way this program can check for lost writes, or acknowledged writes # that were executed. # # Copyright (C) 2013-2014 Salvatore Sanfilippo # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. require 'redis' class ConsistencyTester def initialize(redis) @r = redis @working_set = 10000 @keyspace = 100000 @writes = 0 @reads = 0 @failed_writes = 0 @failed_reads = 0 @lost_writes = 0 @not_ack_writes = 0 @delay = 0 @cached = {} # We take our view of data stored in the DB. @prefix = [Process.pid.to_s,Time.now.usec,@r.object_id,""].join("|") @errtime = {} end def genkey # Write more often to a small subset of keys ks = rand() > 0.5 ? @keyspace : @working_set @prefix+"key_"+rand(ks).to_s end def check_consistency(key,value) expected = @cached[key] return if !expected # We lack info about previous state. if expected > value @lost_writes += expected-value elsif expected < value @not_ack_writes += value-expected end end def puterr(msg) if !@errtime[msg] || Time.now.to_i != @errtime[msg] puts msg end @errtime[msg] = Time.now.to_i end def test last_report = Time.now.to_i while true # Read key = genkey begin val = @r.get(key) check_consistency(key,val.to_i) @reads += 1 rescue => e puterr "Reading: #{e.class}: #{e.message} (#{e.backtrace.first})" @failed_reads += 1 end # Write begin @cached[key] = @r.incr(key).to_i @writes += 1 rescue => e puterr "Writing: #{e.class}: #{e.message} (#{e.backtrace.first})" @failed_writes += 1 end # Report sleep @delay if Time.now.to_i != last_report report = "#{@reads} R (#{@failed_reads} err) | " + "#{@writes} W (#{@failed_writes} err) | " report += "#{@lost_writes} lost | " if @lost_writes > 0 report += "#{@not_ack_writes} noack | " if @not_ack_writes > 0 last_report = Time.now.to_i puts report end end end end Sentinels = [{:host => "127.0.0.1", :port => 26379}, {:host => "127.0.0.1", :port => 26380}] r = Redis.new(:url => "redis://master1", :sentinels => Sentinels, :role => :master) tester = ConsistencyTester.new(r) tester.test redis-3.2.2/examples/list.rb0000644000004100000410000000067312637240307016004 0ustar www-datawww-datarequire 'rubygems' require 'redis' r = Redis.new r.del 'logs' puts p "pushing log messages into a LIST" r.rpush 'logs', 'some log message' r.rpush 'logs', 'another log message' r.rpush 'logs', 'yet another log message' r.rpush 'logs', 'also another log message' puts p 'contents of logs LIST' p r.lrange('logs', 0, -1) puts p 'Trim logs LIST to last 2 elements(easy circular buffer)' r.ltrim('logs', -2, -1) p r.lrange('logs', 0, -1) redis-3.2.2/examples/unicorn/0000755000004100000410000000000012637240307016153 5ustar www-datawww-dataredis-3.2.2/examples/unicorn/unicorn.rb0000644000004100000410000000101712637240307020154 0ustar www-datawww-datarequire "redis" worker_processes 3 # If you set the connection to Redis *before* forking, # you will cause forks to share a file descriptor. # # This causes a concurrency problem by which one fork # can read or write to the socket while others are # performing other operations. # # Most likely you'll be getting ProtocolError exceptions # mentioning a wrong initial byte in the reply. # # Thus we need to connect to Redis after forking the # worker processes. after_fork do |server, worker| Redis.current.disconnect! end redis-3.2.2/examples/unicorn/config.ru0000644000004100000410000000013212637240307017764 0ustar www-datawww-datarun lambda { |env| [200, {"Content-Type" => "text/plain"}, [Redis.current.randomkey]] } redis-3.2.2/examples/sets.rb0000644000004100000410000000076712637240307016013 0ustar www-datawww-datarequire 'rubygems' require 'redis' r = Redis.new r.del 'foo-tags' r.del 'bar-tags' puts p "create a set of tags on foo-tags" r.sadd 'foo-tags', 'one' r.sadd 'foo-tags', 'two' r.sadd 'foo-tags', 'three' puts p "create a set of tags on bar-tags" r.sadd 'bar-tags', 'three' r.sadd 'bar-tags', 'four' r.sadd 'bar-tags', 'five' puts p 'foo-tags' p r.smembers('foo-tags') puts p 'bar-tags' p r.smembers('bar-tags') puts p 'intersection of foo-tags and bar-tags' p r.sinter('foo-tags', 'bar-tags') redis-3.2.2/examples/basic.rb0000644000004100000410000000017412637240307016106 0ustar www-datawww-datarequire 'redis' r = Redis.new r.del('foo') puts p'set foo to "bar"' r['foo'] = 'bar' puts p 'value of foo' p r['foo'] redis-3.2.2/.travis.yml0000644000004100000410000000210112637240307014763 0ustar www-datawww-datalanguage: ruby rvm: - 1.8.7 - 1.9.3 - 2.0 - 2.1 - 2.2 - jruby-18mode - jruby-19mode - rbx-2 gemfile: ".travis/Gemfile" sudo: false env: global: - VERBOSE=true - TIMEOUT=1 matrix: - conn=ruby REDIS_BRANCH=2.8 - conn=hiredis REDIS_BRANCH=2.8 - conn=synchrony REDIS_BRANCH=2.8 - conn=ruby REDIS_BRANCH=unstable branches: only: - master matrix: exclude: # hiredis - rvm: jruby-18mode gemfile: .travis/Gemfile env: conn=hiredis REDIS_BRANCH=2.8 - rvm: jruby-19mode gemfile: .travis/Gemfile env: conn=hiredis REDIS_BRANCH=2.8 # synchrony - rvm: 1.8.7 gemfile: .travis/Gemfile env: conn=synchrony REDIS_BRANCH=2.8 - rvm: jruby-18mode gemfile: .travis/Gemfile env: conn=synchrony REDIS_BRANCH=2.8 - rvm: jruby-19mode gemfile: .travis/Gemfile env: conn=synchrony REDIS_BRANCH=2.8 allow_failures: - rvm: rbx-2 notifications: irc: - irc.freenode.net#redis-rb email: - damian.janowski@gmail.com - pcnoordhuis@gmail.com redis-3.2.2/lib/0000755000004100000410000000000012637240307013426 5ustar www-datawww-dataredis-3.2.2/lib/redis.rb0000644000004100000410000022371712637240307015075 0ustar www-datawww-datarequire "monitor" require "redis/errors" class Redis def self.deprecate(message, trace = caller[0]) $stderr.puts "\n#{message} (in #{trace})" end attr :client # @deprecated The preferred way to create a new client object is using `#new`. # This method does not actually establish a connection to Redis, # in contrary to what you might expect. def self.connect(options = {}) new(options) end def self.current @current ||= Redis.new end def self.current=(redis) @current = redis end include MonitorMixin # Create a new client instance # # @param [Hash] options # @option options [String] :url (value of the environment variable REDIS_URL) a Redis URL, for a TCP connection: `redis://:[password]@[hostname]:[port]/[db]` (password, port and database are optional), for a unix socket connection: `unix://[path to Redis socket]`. This overrides all other options. # @option options [String] :host ("127.0.0.1") server hostname # @option options [Fixnum] :port (6379) server port # @option options [String] :path path to server socket (overrides host and port) # @option options [Float] :timeout (5.0) timeout in seconds # @option options [Float] :connect_timeout (same as timeout) timeout for initial connect in seconds # @option options [String] :password Password to authenticate against server # @option options [Fixnum] :db (0) Database to select after initial connect # @option options [Symbol] :driver Driver to use, currently supported: `:ruby`, `:hiredis`, `:synchrony` # @option options [String] :id ID for the client connection, assigns name to current connection by sending `CLIENT SETNAME` # @option options [Hash, Fixnum] :tcp_keepalive Keepalive values, if Fixnum `intvl` and `probe` are calculated based on the value, if Hash `time`, `intvl` and `probes` can be specified as a Fixnum # @option options [Fixnum] :reconnect_attempts Number of attempts trying to connect # @option options [Boolean] :inherit_socket (false) Whether to use socket in forked process or not # @option options [Array] :sentinels List of sentinels to contact # @option options [Symbol] :role (:master) Role to fetch via Sentinel, either `:master` or `:slave` # # @return [Redis] a new client instance def initialize(options = {}) @options = options.dup @original_client = @client = Client.new(options) super() # Monitor#initialize end def synchronize mon_synchronize { yield(@client) } end # Run code with the client reconnecting def with_reconnect(val=true, &blk) synchronize do |client| client.with_reconnect(val, &blk) end end # Run code without the client reconnecting def without_reconnect(&blk) with_reconnect(false, &blk) end # Test whether or not the client is connected def connected? @original_client.connected? end # Disconnect the client as quickly and silently as possible. def disconnect! @original_client.disconnect end # Authenticate to the server. # # @param [String] password must match the password specified in the # `requirepass` directive in the configuration file # @return [String] `OK` def auth(password) synchronize do |client| client.call([:auth, password]) end end # Change the selected database for the current connection. # # @param [Fixnum] db zero-based index of the DB to use (0 to 15) # @return [String] `OK` def select(db) synchronize do |client| client.db = db client.call([:select, db]) end end # Ping the server. # # @return [String] `PONG` def ping synchronize do |client| client.call([:ping]) end end # Echo the given string. # # @param [String] value # @return [String] def echo(value) synchronize do |client| client.call([:echo, value]) end end # Close the connection. # # @return [String] `OK` def quit synchronize do |client| begin client.call([:quit]) rescue ConnectionError ensure client.disconnect end end end # Asynchronously rewrite the append-only file. # # @return [String] `OK` def bgrewriteaof synchronize do |client| client.call([:bgrewriteaof]) end end # Asynchronously save the dataset to disk. # # @return [String] `OK` def bgsave synchronize do |client| client.call([:bgsave]) end end # Get or set server configuration parameters. # # @param [Symbol] action e.g. `:get`, `:set`, `:resetstat` # @return [String, Hash] string reply, or hash when retrieving more than one # property with `CONFIG GET` def config(action, *args) synchronize do |client| client.call([:config, action] + args) do |reply| if reply.kind_of?(Array) && action == :get Hash[_pairify(reply)] else reply end end end end # Return the number of keys in the selected database. # # @return [Fixnum] def dbsize synchronize do |client| client.call([:dbsize]) end end def debug(*args) synchronize do |client| client.call([:debug] + args) end end # Remove all keys from all databases. # # @return [String] `OK` def flushall synchronize do |client| client.call([:flushall]) end end # Remove all keys from the current database. # # @return [String] `OK` def flushdb synchronize do |client| client.call([:flushdb]) end end # Get information and statistics about the server. # # @param [String, Symbol] cmd e.g. "commandstats" # @return [Hash] def info(cmd = nil) synchronize do |client| client.call([:info, cmd].compact) do |reply| if reply.kind_of?(String) reply = Hash[reply.split("\r\n").map do |line| line.split(":", 2) unless line =~ /^(#|$)/ end.compact] if cmd && cmd.to_s == "commandstats" # Extract nested hashes for INFO COMMANDSTATS reply = Hash[reply.map do |k, v| v = v.split(",").map { |e| e.split("=") } [k[/^cmdstat_(.*)$/, 1], Hash[v]] end] end end reply end end end # Get the UNIX time stamp of the last successful save to disk. # # @return [Fixnum] def lastsave synchronize do |client| client.call([:lastsave]) end end # Listen for all requests received by the server in real time. # # There is no way to interrupt this command. # # @yield a block to be called for every line of output # @yieldparam [String] line timestamp and command that was executed def monitor(&block) synchronize do |client| client.call_loop([:monitor], &block) end end # Synchronously save the dataset to disk. # # @return [String] def save synchronize do |client| client.call([:save]) end end # Synchronously save the dataset to disk and then shut down the server. def shutdown synchronize do |client| client.with_reconnect(false) do begin client.call([:shutdown]) rescue ConnectionError # This means Redis has probably exited. nil end end end end # Make the server a slave of another instance, or promote it as master. def slaveof(host, port) synchronize do |client| client.call([:slaveof, host, port]) end end # Interact with the slowlog (get, len, reset) # # @param [String] subcommand e.g. `get`, `len`, `reset` # @param [Fixnum] length maximum number of entries to return # @return [Array, Fixnum, String] depends on subcommand def slowlog(subcommand, length=nil) synchronize do |client| args = [:slowlog, subcommand] args << length if length client.call args end end # Internal command used for replication. def sync synchronize do |client| client.call([:sync]) end end # Return the server time. # # @example # r.time # => [ 1333093196, 606806 ] # # @return [Array] tuple of seconds since UNIX epoch and # microseconds in the current second def time synchronize do |client| client.call([:time]) do |reply| reply.map(&:to_i) if reply end end end # Remove the expiration from a key. # # @param [String] key # @return [Boolean] whether the timeout was removed or not def persist(key) synchronize do |client| client.call([:persist, key], &_boolify) end end # Set a key's time to live in seconds. # # @param [String] key # @param [Fixnum] seconds time to live # @return [Boolean] whether the timeout was set or not def expire(key, seconds) synchronize do |client| client.call([:expire, key, seconds], &_boolify) end end # Set the expiration for a key as a UNIX timestamp. # # @param [String] key # @param [Fixnum] unix_time expiry time specified as a UNIX timestamp # @return [Boolean] whether the timeout was set or not def expireat(key, unix_time) synchronize do |client| client.call([:expireat, key, unix_time], &_boolify) end end # Get the time to live (in seconds) for a key. # # @param [String] key # @return [Fixnum] remaining time to live in seconds. # # In Redis 2.6 or older the command returns -1 if the key does not exist or if # the key exist but has no associated expire. # # Starting with Redis 2.8 the return value in case of error changed: # # - The command returns -2 if the key does not exist. # - The command returns -1 if the key exists but has no associated expire. def ttl(key) synchronize do |client| client.call([:ttl, key]) end end # Set a key's time to live in milliseconds. # # @param [String] key # @param [Fixnum] milliseconds time to live # @return [Boolean] whether the timeout was set or not def pexpire(key, milliseconds) synchronize do |client| client.call([:pexpire, key, milliseconds], &_boolify) end end # Set the expiration for a key as number of milliseconds from UNIX Epoch. # # @param [String] key # @param [Fixnum] ms_unix_time expiry time specified as number of milliseconds from UNIX Epoch. # @return [Boolean] whether the timeout was set or not def pexpireat(key, ms_unix_time) synchronize do |client| client.call([:pexpireat, key, ms_unix_time], &_boolify) end end # Get the time to live (in milliseconds) for a key. # # @param [String] key # @return [Fixnum] remaining time to live in milliseconds # In Redis 2.6 or older the command returns -1 if the key does not exist or if # the key exist but has no associated expire. # # Starting with Redis 2.8 the return value in case of error changed: # # - The command returns -2 if the key does not exist. # - The command returns -1 if the key exists but has no associated expire. def pttl(key) synchronize do |client| client.call([:pttl, key]) end end # Return a serialized version of the value stored at a key. # # @param [String] key # @return [String] serialized_value def dump(key) synchronize do |client| client.call([:dump, key]) end end # Create a key using the serialized value, previously obtained using DUMP. # # @param [String] key # @param [String] ttl # @param [String] serialized_value # @return [String] `"OK"` def restore(key, ttl, serialized_value) synchronize do |client| client.call([:restore, key, ttl, serialized_value]) end end # Transfer a key from the connected instance to another instance. # # @param [String] key # @param [Hash] options # - `:host => String`: host of instance to migrate to # - `:port => Integer`: port of instance to migrate to # - `:db => Integer`: database to migrate to (default: same as source) # - `:timeout => Integer`: timeout (default: same as connection timeout) # @return [String] `"OK"` def migrate(key, options) host = options[:host] || raise(RuntimeError, ":host not specified") port = options[:port] || raise(RuntimeError, ":port not specified") db = (options[:db] || client.db).to_i timeout = (options[:timeout] || client.timeout).to_i synchronize do |client| client.call([:migrate, host, port, key, db, timeout]) end end # Delete one or more keys. # # @param [String, Array] keys # @return [Fixnum] number of keys that were deleted def del(*keys) synchronize do |client| client.call([:del] + keys) end end # Determine if a key exists. # # @param [String] key # @return [Boolean] def exists(key) synchronize do |client| client.call([:exists, key], &_boolify) end end # Find all keys matching the given pattern. # # @param [String] pattern # @return [Array] def keys(pattern = "*") synchronize do |client| client.call([:keys, pattern]) do |reply| if reply.kind_of?(String) reply.split(" ") else reply end end end end # Move a key to another database. # # @example Move a key to another database # redis.set "foo", "bar" # # => "OK" # redis.move "foo", 2 # # => true # redis.exists "foo" # # => false # redis.select 2 # # => "OK" # redis.exists "foo" # # => true # redis.get "foo" # # => "bar" # # @param [String] key # @param [Fixnum] db # @return [Boolean] whether the key was moved or not def move(key, db) synchronize do |client| client.call([:move, key, db], &_boolify) end end def object(*args) synchronize do |client| client.call([:object] + args) end end # Return a random key from the keyspace. # # @return [String] def randomkey synchronize do |client| client.call([:randomkey]) end end # Rename a key. If the new key already exists it is overwritten. # # @param [String] old_name # @param [String] new_name # @return [String] `OK` def rename(old_name, new_name) synchronize do |client| client.call([:rename, old_name, new_name]) end end # Rename a key, only if the new key does not exist. # # @param [String] old_name # @param [String] new_name # @return [Boolean] whether the key was renamed or not def renamenx(old_name, new_name) synchronize do |client| client.call([:renamenx, old_name, new_name], &_boolify) end end # Sort the elements in a list, set or sorted set. # # @example Retrieve the first 2 elements from an alphabetically sorted "list" # redis.sort("list", :order => "alpha", :limit => [0, 2]) # # => ["a", "b"] # @example Store an alphabetically descending list in "target" # redis.sort("list", :order => "desc alpha", :store => "target") # # => 26 # # @param [String] key # @param [Hash] options # - `:by => String`: use external key to sort elements by # - `:limit => [offset, count]`: skip `offset` elements, return a maximum # of `count` elements # - `:get => [String, Array]`: single key or array of keys to # retrieve per element in the result # - `:order => String`: combination of `ASC`, `DESC` and optionally `ALPHA` # - `:store => String`: key to store the result at # # @return [Array, Array>, Fixnum] # - when `:get` is not specified, or holds a single element, an array of elements # - when `:get` is specified, and holds more than one element, an array of # elements where every element is an array with the result for every # element specified in `:get` # - when `:store` is specified, the number of elements in the stored result def sort(key, options = {}) args = [] by = options[:by] args.concat(["BY", by]) if by limit = options[:limit] args.concat(["LIMIT"] + limit) if limit get = Array(options[:get]) args.concat(["GET"].product(get).flatten) unless get.empty? order = options[:order] args.concat(order.split(" ")) if order store = options[:store] args.concat(["STORE", store]) if store synchronize do |client| client.call([:sort, key] + args) do |reply| if get.size > 1 && !store if reply reply.each_slice(get.size).to_a end else reply end end end end # Determine the type stored at key. # # @param [String] key # @return [String] `string`, `list`, `set`, `zset`, `hash` or `none` def type(key) synchronize do |client| client.call([:type, key]) end end # Decrement the integer value of a key by one. # # @example # redis.decr("value") # # => 4 # # @param [String] key # @return [Fixnum] value after decrementing it def decr(key) synchronize do |client| client.call([:decr, key]) end end # Decrement the integer value of a key by the given number. # # @example # redis.decrby("value", 5) # # => 0 # # @param [String] key # @param [Fixnum] decrement # @return [Fixnum] value after decrementing it def decrby(key, decrement) synchronize do |client| client.call([:decrby, key, decrement]) end end # Increment the integer value of a key by one. # # @example # redis.incr("value") # # => 6 # # @param [String] key # @return [Fixnum] value after incrementing it def incr(key) synchronize do |client| client.call([:incr, key]) end end # Increment the integer value of a key by the given integer number. # # @example # redis.incrby("value", 5) # # => 10 # # @param [String] key # @param [Fixnum] increment # @return [Fixnum] value after incrementing it def incrby(key, increment) synchronize do |client| client.call([:incrby, key, increment]) end end # Increment the numeric value of a key by the given float number. # # @example # redis.incrbyfloat("value", 1.23) # # => 1.23 # # @param [String] key # @param [Float] increment # @return [Float] value after incrementing it def incrbyfloat(key, increment) synchronize do |client| client.call([:incrbyfloat, key, increment], &_floatify) end end # Set the string value of a key. # # @param [String] key # @param [String] value # @param [Hash] options # - `:ex => Fixnum`: Set the specified expire time, in seconds. # - `:px => Fixnum`: Set the specified expire time, in milliseconds. # - `:nx => true`: Only set the key if it does not already exist. # - `:xx => true`: Only set the key if it already exist. # @return [String, Boolean] `"OK"` or true, false if `:nx => true` or `:xx => true` def set(key, value, options = {}) args = [] ex = options[:ex] args.concat(["EX", ex]) if ex px = options[:px] args.concat(["PX", px]) if px nx = options[:nx] args.concat(["NX"]) if nx xx = options[:xx] args.concat(["XX"]) if xx synchronize do |client| if nx || xx client.call([:set, key, value.to_s] + args, &_boolify_set) else client.call([:set, key, value.to_s] + args) end end end alias :[]= :set # Set the time to live in seconds of a key. # # @param [String] key # @param [Fixnum] ttl # @param [String] value # @return [String] `"OK"` def setex(key, ttl, value) synchronize do |client| client.call([:setex, key, ttl, value.to_s]) end end # Set the time to live in milliseconds of a key. # # @param [String] key # @param [Fixnum] ttl # @param [String] value # @return [String] `"OK"` def psetex(key, ttl, value) synchronize do |client| client.call([:psetex, key, ttl, value.to_s]) end end # Set the value of a key, only if the key does not exist. # # @param [String] key # @param [String] value # @return [Boolean] whether the key was set or not def setnx(key, value) synchronize do |client| client.call([:setnx, key, value.to_s], &_boolify) end end # Set one or more values. # # @example # redis.mset("key1", "v1", "key2", "v2") # # => "OK" # # @param [Array] args array of keys and values # @return [String] `"OK"` # # @see #mapped_mset def mset(*args) synchronize do |client| client.call([:mset] + args) end end # Set one or more values. # # @example # redis.mapped_mset({ "f1" => "v1", "f2" => "v2" }) # # => "OK" # # @param [Hash] hash keys mapping to values # @return [String] `"OK"` # # @see #mset def mapped_mset(hash) mset(hash.to_a.flatten) end # Set one or more values, only if none of the keys exist. # # @example # redis.msetnx("key1", "v1", "key2", "v2") # # => true # # @param [Array] args array of keys and values # @return [Boolean] whether or not all values were set # # @see #mapped_msetnx def msetnx(*args) synchronize do |client| client.call([:msetnx] + args, &_boolify) end end # Set one or more values, only if none of the keys exist. # # @example # redis.mapped_msetnx({ "key1" => "v1", "key2" => "v2" }) # # => true # # @param [Hash] hash keys mapping to values # @return [Boolean] whether or not all values were set # # @see #msetnx def mapped_msetnx(hash) msetnx(hash.to_a.flatten) end # Get the value of a key. # # @param [String] key # @return [String] def get(key) synchronize do |client| client.call([:get, key]) end end alias :[] :get # Get the values of all the given keys. # # @example # redis.mget("key1", "key1") # # => ["v1", "v2"] # # @param [Array] keys # @return [Array] an array of values for the specified keys # # @see #mapped_mget def mget(*keys, &blk) synchronize do |client| client.call([:mget] + keys, &blk) end end # Get the values of all the given keys. # # @example # redis.mapped_mget("key1", "key1") # # => { "key1" => "v1", "key2" => "v2" } # # @param [Array] keys array of keys # @return [Hash] a hash mapping the specified keys to their values # # @see #mget def mapped_mget(*keys) mget(*keys) do |reply| if reply.kind_of?(Array) Hash[keys.zip(reply)] else reply end end end # Overwrite part of a string at key starting at the specified offset. # # @param [String] key # @param [Fixnum] offset byte offset # @param [String] value # @return [Fixnum] length of the string after it was modified def setrange(key, offset, value) synchronize do |client| client.call([:setrange, key, offset, value.to_s]) end end # Get a substring of the string stored at a key. # # @param [String] key # @param [Fixnum] start zero-based start offset # @param [Fixnum] stop zero-based end offset. Use -1 for representing # the end of the string # @return [Fixnum] `0` or `1` def getrange(key, start, stop) synchronize do |client| client.call([:getrange, key, start, stop]) end end # Sets or clears the bit at offset in the string value stored at key. # # @param [String] key # @param [Fixnum] offset bit offset # @param [Fixnum] value bit value `0` or `1` # @return [Fixnum] the original bit value stored at `offset` def setbit(key, offset, value) synchronize do |client| client.call([:setbit, key, offset, value]) end end # Returns the bit value at offset in the string value stored at key. # # @param [String] key # @param [Fixnum] offset bit offset # @return [Fixnum] `0` or `1` def getbit(key, offset) synchronize do |client| client.call([:getbit, key, offset]) end end # Append a value to a key. # # @param [String] key # @param [String] value value to append # @return [Fixnum] length of the string after appending def append(key, value) synchronize do |client| client.call([:append, key, value]) end end # Count the number of set bits in a range of the string value stored at key. # # @param [String] key # @param [Fixnum] start start index # @param [Fixnum] stop stop index # @return [Fixnum] the number of bits set to 1 def bitcount(key, start = 0, stop = -1) synchronize do |client| client.call([:bitcount, key, start, stop]) end end # Perform a bitwise operation between strings and store the resulting string in a key. # # @param [String] operation e.g. `and`, `or`, `xor`, `not` # @param [String] destkey destination key # @param [String, Array] keys one or more source keys to perform `operation` # @return [Fixnum] the length of the string stored in `destkey` def bitop(operation, destkey, *keys) synchronize do |client| client.call([:bitop, operation, destkey] + keys) end end # Return the position of the first bit set to 1 or 0 in a string. # # @param [String] key # @param [Fixnum] bit whether to look for the first 1 or 0 bit # @param [Fixnum] start start index # @param [Fixnum] stop stop index # @return [Fixnum] the position of the first 1/0 bit. # -1 if looking for 1 and it is not found or start and stop are given. def bitpos(key, bit, start=nil, stop=nil) if stop and not start raise(ArgumentError, 'stop parameter specified without start parameter') end synchronize do |client| command = [:bitpos, key, bit] command << start if start command << stop if stop client.call(command) end end # Set the string value of a key and return its old value. # # @param [String] key # @param [String] value value to replace the current value with # @return [String] the old value stored in the key, or `nil` if the key # did not exist def getset(key, value) synchronize do |client| client.call([:getset, key, value.to_s]) end end # Get the length of the value stored in a key. # # @param [String] key # @return [Fixnum] the length of the value stored in the key, or 0 # if the key does not exist def strlen(key) synchronize do |client| client.call([:strlen, key]) end end # Get the length of a list. # # @param [String] key # @return [Fixnum] def llen(key) synchronize do |client| client.call([:llen, key]) end end # Prepend one or more values to a list, creating the list if it doesn't exist # # @param [String] key # @param [String, Array] value string value, or array of string values to push # @return [Fixnum] the length of the list after the push operation def lpush(key, value) synchronize do |client| client.call([:lpush, key, value]) end end # Prepend a value to a list, only if the list exists. # # @param [String] key # @param [String] value # @return [Fixnum] the length of the list after the push operation def lpushx(key, value) synchronize do |client| client.call([:lpushx, key, value]) end end # Append one or more values to a list, creating the list if it doesn't exist # # @param [String] key # @param [String] value # @return [Fixnum] the length of the list after the push operation def rpush(key, value) synchronize do |client| client.call([:rpush, key, value]) end end # Append a value to a list, only if the list exists. # # @param [String] key # @param [String] value # @return [Fixnum] the length of the list after the push operation def rpushx(key, value) synchronize do |client| client.call([:rpushx, key, value]) end end # Remove and get the first element in a list. # # @param [String] key # @return [String] def lpop(key) synchronize do |client| client.call([:lpop, key]) end end # Remove and get the last element in a list. # # @param [String] key # @return [String] def rpop(key) synchronize do |client| client.call([:rpop, key]) end end # Remove the last element in a list, append it to another list and return it. # # @param [String] source source key # @param [String] destination destination key # @return [nil, String] the element, or nil when the source key does not exist def rpoplpush(source, destination) synchronize do |client| client.call([:rpoplpush, source, destination]) end end def _bpop(cmd, args) options = {} case args.last when Hash options = args.pop when Integer # Issue deprecation notice in obnoxious mode... options[:timeout] = args.pop end if args.size > 1 # Issue deprecation notice in obnoxious mode... end keys = args.flatten timeout = options[:timeout] || 0 synchronize do |client| command = [cmd, keys, timeout] timeout += client.timeout if timeout > 0 client.call_with_timeout(command, timeout) end end # Remove and get the first element in a list, or block until one is available. # # @example With timeout # list, element = redis.blpop("list", :timeout => 5) # # => nil on timeout # # => ["list", "element"] on success # @example Without timeout # list, element = redis.blpop("list") # # => ["list", "element"] # @example Blocking pop on multiple lists # list, element = redis.blpop(["list", "another_list"]) # # => ["list", "element"] # # @param [String, Array] keys one or more keys to perform the # blocking pop on # @param [Hash] options # - `:timeout => Fixnum`: timeout in seconds, defaults to no timeout # # @return [nil, [String, String]] # - `nil` when the operation timed out # - tuple of the list that was popped from and element was popped otherwise def blpop(*args) _bpop(:blpop, args) end # Remove and get the last element in a list, or block until one is available. # # @param [String, Array] keys one or more keys to perform the # blocking pop on # @param [Hash] options # - `:timeout => Fixnum`: timeout in seconds, defaults to no timeout # # @return [nil, [String, String]] # - `nil` when the operation timed out # - tuple of the list that was popped from and element was popped otherwise # # @see #blpop def brpop(*args) _bpop(:brpop, args) end # Pop a value from a list, push it to another list and return it; or block # until one is available. # # @param [String] source source key # @param [String] destination destination key # @param [Hash] options # - `:timeout => Fixnum`: timeout in seconds, defaults to no timeout # # @return [nil, String] # - `nil` when the operation timed out # - the element was popped and pushed otherwise def brpoplpush(source, destination, options = {}) case options when Integer # Issue deprecation notice in obnoxious mode... options = { :timeout => options } end timeout = options[:timeout] || 0 synchronize do |client| command = [:brpoplpush, source, destination, timeout] timeout += client.timeout if timeout > 0 client.call_with_timeout(command, timeout) end end # Get an element from a list by its index. # # @param [String] key # @param [Fixnum] index # @return [String] def lindex(key, index) synchronize do |client| client.call([:lindex, key, index]) end end # Insert an element before or after another element in a list. # # @param [String] key # @param [String, Symbol] where `BEFORE` or `AFTER` # @param [String] pivot reference element # @param [String] value # @return [Fixnum] length of the list after the insert operation, or `-1` # when the element `pivot` was not found def linsert(key, where, pivot, value) synchronize do |client| client.call([:linsert, key, where, pivot, value]) end end # Get a range of elements from a list. # # @param [String] key # @param [Fixnum] start start index # @param [Fixnum] stop stop index # @return [Array] def lrange(key, start, stop) synchronize do |client| client.call([:lrange, key, start, stop]) end end # Remove elements from a list. # # @param [String] key # @param [Fixnum] count number of elements to remove. Use a positive # value to remove the first `count` occurrences of `value`. A negative # value to remove the last `count` occurrences of `value`. Or zero, to # remove all occurrences of `value` from the list. # @param [String] value # @return [Fixnum] the number of removed elements def lrem(key, count, value) synchronize do |client| client.call([:lrem, key, count, value]) end end # Set the value of an element in a list by its index. # # @param [String] key # @param [Fixnum] index # @param [String] value # @return [String] `OK` def lset(key, index, value) synchronize do |client| client.call([:lset, key, index, value]) end end # Trim a list to the specified range. # # @param [String] key # @param [Fixnum] start start index # @param [Fixnum] stop stop index # @return [String] `OK` def ltrim(key, start, stop) synchronize do |client| client.call([:ltrim, key, start, stop]) end end # Get the number of members in a set. # # @param [String] key # @return [Fixnum] def scard(key) synchronize do |client| client.call([:scard, key]) end end # Add one or more members to a set. # # @param [String] key # @param [String, Array] member one member, or array of members # @return [Boolean, Fixnum] `Boolean` when a single member is specified, # holding whether or not adding the member succeeded, or `Fixnum` when an # array of members is specified, holding the number of members that were # successfully added def sadd(key, member) synchronize do |client| client.call([:sadd, key, member]) do |reply| if member.is_a? Array # Variadic: return integer reply else # Single argument: return boolean _boolify.call(reply) end end end end # Remove one or more members from a set. # # @param [String] key # @param [String, Array] member one member, or array of members # @return [Boolean, Fixnum] `Boolean` when a single member is specified, # holding whether or not removing the member succeeded, or `Fixnum` when an # array of members is specified, holding the number of members that were # successfully removed def srem(key, member) synchronize do |client| client.call([:srem, key, member]) do |reply| if member.is_a? Array # Variadic: return integer reply else # Single argument: return boolean _boolify.call(reply) end end end end # Remove and return a random member from a set. # # @param [String] key # @return [String] def spop(key) synchronize do |client| client.call([:spop, key]) end end # Get one or more random members from a set. # # @param [String] key # @param [Fixnum] count # @return [String] def srandmember(key, count = nil) synchronize do |client| if count.nil? client.call([:srandmember, key]) else client.call([:srandmember, key, count]) end end end # Move a member from one set to another. # # @param [String] source source key # @param [String] destination destination key # @param [String] member member to move from `source` to `destination` # @return [Boolean] def smove(source, destination, member) synchronize do |client| client.call([:smove, source, destination, member], &_boolify) end end # Determine if a given value is a member of a set. # # @param [String] key # @param [String] member # @return [Boolean] def sismember(key, member) synchronize do |client| client.call([:sismember, key, member], &_boolify) end end # Get all the members in a set. # # @param [String] key # @return [Array] def smembers(key) synchronize do |client| client.call([:smembers, key]) end end # Subtract multiple sets. # # @param [String, Array] keys keys pointing to sets to subtract # @return [Array] members in the difference def sdiff(*keys) synchronize do |client| client.call([:sdiff] + keys) end end # Subtract multiple sets and store the resulting set in a key. # # @param [String] destination destination key # @param [String, Array] keys keys pointing to sets to subtract # @return [Fixnum] number of elements in the resulting set def sdiffstore(destination, *keys) synchronize do |client| client.call([:sdiffstore, destination] + keys) end end # Intersect multiple sets. # # @param [String, Array] keys keys pointing to sets to intersect # @return [Array] members in the intersection def sinter(*keys) synchronize do |client| client.call([:sinter] + keys) end end # Intersect multiple sets and store the resulting set in a key. # # @param [String] destination destination key # @param [String, Array] keys keys pointing to sets to intersect # @return [Fixnum] number of elements in the resulting set def sinterstore(destination, *keys) synchronize do |client| client.call([:sinterstore, destination] + keys) end end # Add multiple sets. # # @param [String, Array] keys keys pointing to sets to unify # @return [Array] members in the union def sunion(*keys) synchronize do |client| client.call([:sunion] + keys) end end # Add multiple sets and store the resulting set in a key. # # @param [String] destination destination key # @param [String, Array] keys keys pointing to sets to unify # @return [Fixnum] number of elements in the resulting set def sunionstore(destination, *keys) synchronize do |client| client.call([:sunionstore, destination] + keys) end end # Get the number of members in a sorted set. # # @example # redis.zcard("zset") # # => 4 # # @param [String] key # @return [Fixnum] def zcard(key) synchronize do |client| client.call([:zcard, key]) end end # Add one or more members to a sorted set, or update the score for members # that already exist. # # @example Add a single `[score, member]` pair to a sorted set # redis.zadd("zset", 32.0, "member") # @example Add an array of `[score, member]` pairs to a sorted set # redis.zadd("zset", [[32.0, "a"], [64.0, "b"]]) # # @param [String] key # @param [[Float, String], Array<[Float, String]>] args # - a single `[score, member]` pair # - an array of `[score, member]` pairs # @param [Hash] options # - `:xx => true`: Only update elements that already exist (never # add elements) # - `:nx => true`: Don't update already existing elements (always # add new elements) # - `:ch => true`: Modify the return value from the number of new # elements added, to the total number of elements changed (CH is an # abbreviation of changed); changed elements are new elements added # and elements already existing for which the score was updated # - `:incr => true`: When this option is specified ZADD acts like # ZINCRBY; only one score-element pair can be specified in this mode # # @return [Boolean, Fixnum, Float] # - `Boolean` when a single pair is specified, holding whether or not it was # **added** to the sorted set. # - `Fixnum` when an array of pairs is specified, holding the number of # pairs that were **added** to the sorted set. # - `Float` when option :incr is specified, holding the score of the member # after incrementing it. def zadd(key, *args) #, options zadd_options = [] if args.last.is_a?(Hash) options = args.pop nx = options[:nx] zadd_options << "NX" if nx xx = options[:xx] zadd_options << "XX" if xx ch = options[:ch] zadd_options << "CH" if ch incr = options[:incr] zadd_options << "INCR" if incr end synchronize do |client| if args.size == 1 && args[0].is_a?(Array) # Variadic: return float if INCR, integer if !INCR client.call([:zadd, key] + zadd_options + args[0], &(incr ? _floatify : _identity)) elsif args.size == 2 # Single pair: return float if INCR, boolean if !INCR client.call([:zadd, key] + zadd_options + args, &(incr ? _floatify : _boolify)) else raise ArgumentError, "wrong number of arguments" end end end # Increment the score of a member in a sorted set. # # @example # redis.zincrby("zset", 32.0, "a") # # => 64.0 # # @param [String] key # @param [Float] increment # @param [String] member # @return [Float] score of the member after incrementing it def zincrby(key, increment, member) synchronize do |client| client.call([:zincrby, key, increment, member], &_floatify) end end # Remove one or more members from a sorted set. # # @example Remove a single member from a sorted set # redis.zrem("zset", "a") # @example Remove an array of members from a sorted set # redis.zrem("zset", ["a", "b"]) # # @param [String] key # @param [String, Array] member # - a single member # - an array of members # # @return [Boolean, Fixnum] # - `Boolean` when a single member is specified, holding whether or not it # was removed from the sorted set # - `Fixnum` when an array of pairs is specified, holding the number of # members that were removed to the sorted set def zrem(key, member) synchronize do |client| client.call([:zrem, key, member]) do |reply| if member.is_a? Array # Variadic: return integer reply else # Single argument: return boolean _boolify.call(reply) end end end end # Get the score associated with the given member in a sorted set. # # @example Get the score for member "a" # redis.zscore("zset", "a") # # => 32.0 # # @param [String] key # @param [String] member # @return [Float] score of the member def zscore(key, member) synchronize do |client| client.call([:zscore, key, member], &_floatify) end end # Return a range of members in a sorted set, by index. # # @example Retrieve all members from a sorted set # redis.zrange("zset", 0, -1) # # => ["a", "b"] # @example Retrieve all members and their scores from a sorted set # redis.zrange("zset", 0, -1, :with_scores => true) # # => [["a", 32.0], ["b", 64.0]] # # @param [String] key # @param [Fixnum] start start index # @param [Fixnum] stop stop index # @param [Hash] options # - `:with_scores => true`: include scores in output # # @return [Array, Array<[String, Float]>] # - when `:with_scores` is not specified, an array of members # - when `:with_scores` is specified, an array with `[member, score]` pairs def zrange(key, start, stop, options = {}) args = [] with_scores = options[:with_scores] || options[:withscores] if with_scores args << "WITHSCORES" block = _floatify_pairs end synchronize do |client| client.call([:zrange, key, start, stop] + args, &block) end end # Return a range of members in a sorted set, by index, with scores ordered # from high to low. # # @example Retrieve all members from a sorted set # redis.zrevrange("zset", 0, -1) # # => ["b", "a"] # @example Retrieve all members and their scores from a sorted set # redis.zrevrange("zset", 0, -1, :with_scores => true) # # => [["b", 64.0], ["a", 32.0]] # # @see #zrange def zrevrange(key, start, stop, options = {}) args = [] with_scores = options[:with_scores] || options[:withscores] if with_scores args << "WITHSCORES" block = _floatify_pairs end synchronize do |client| client.call([:zrevrange, key, start, stop] + args, &block) end end # Determine the index of a member in a sorted set. # # @param [String] key # @param [String] member # @return [Fixnum] def zrank(key, member) synchronize do |client| client.call([:zrank, key, member]) end end # Determine the index of a member in a sorted set, with scores ordered from # high to low. # # @param [String] key # @param [String] member # @return [Fixnum] def zrevrank(key, member) synchronize do |client| client.call([:zrevrank, key, member]) end end # Remove all members in a sorted set within the given indexes. # # @example Remove first 5 members # redis.zremrangebyrank("zset", 0, 4) # # => 5 # @example Remove last 5 members # redis.zremrangebyrank("zset", -5, -1) # # => 5 # # @param [String] key # @param [Fixnum] start start index # @param [Fixnum] stop stop index # @return [Fixnum] number of members that were removed def zremrangebyrank(key, start, stop) synchronize do |client| client.call([:zremrangebyrank, key, start, stop]) end end # Return a range of members with the same score in a sorted set, by lexicographical ordering # # @example Retrieve members matching a # redis.zrangebylex("zset", "[a", "[a\xff") # # => ["aaren", "aarika", "abagael", "abby"] # @example Retrieve the first 2 members matching a # redis.zrangebylex("zset", "[a", "[a\xff", :limit => [0, 2]) # # => ["aaren", "aarika"] # # @param [String] key # @param [String] min # - inclusive minimum is specified by prefixing `(` # - exclusive minimum is specified by prefixing `[` # @param [String] max # - inclusive maximum is specified by prefixing `(` # - exclusive maximum is specified by prefixing `[` # @param [Hash] options # - `:limit => [offset, count]`: skip `offset` members, return a maximum of # `count` members # # @return [Array, Array<[String, Float]>] def zrangebylex(key, min, max, options = {}) args = [] limit = options[:limit] args.concat(["LIMIT"] + limit) if limit synchronize do |client| client.call([:zrangebylex, key, min, max] + args) end end # Return a range of members with the same score in a sorted set, by reversed lexicographical ordering. # Apart from the reversed ordering, #zrevrangebylex is similar to #zrangebylex. # # @example Retrieve members matching a # redis.zrevrangebylex("zset", "[a", "[a\xff") # # => ["abbygail", "abby", "abagael", "aaren"] # @example Retrieve the last 2 members matching a # redis.zrevrangebylex("zset", "[a", "[a\xff", :limit => [0, 2]) # # => ["abbygail", "abby"] # # @see #zrangebylex def zrevrangebylex(key, max, min, options = {}) args = [] limit = options[:limit] args.concat(["LIMIT"] + limit) if limit synchronize do |client| client.call([:zrevrangebylex, key, max, min] + args) end end # Return a range of members in a sorted set, by score. # # @example Retrieve members with score `>= 5` and `< 100` # redis.zrangebyscore("zset", "5", "(100") # # => ["a", "b"] # @example Retrieve the first 2 members with score `>= 0` # redis.zrangebyscore("zset", "0", "+inf", :limit => [0, 2]) # # => ["a", "b"] # @example Retrieve members and their scores with scores `> 5` # redis.zrangebyscore("zset", "(5", "+inf", :with_scores => true) # # => [["a", 32.0], ["b", 64.0]] # # @param [String] key # @param [String] min # - inclusive minimum score is specified verbatim # - exclusive minimum score is specified by prefixing `(` # @param [String] max # - inclusive maximum score is specified verbatim # - exclusive maximum score is specified by prefixing `(` # @param [Hash] options # - `:with_scores => true`: include scores in output # - `:limit => [offset, count]`: skip `offset` members, return a maximum of # `count` members # # @return [Array, Array<[String, Float]>] # - when `:with_scores` is not specified, an array of members # - when `:with_scores` is specified, an array with `[member, score]` pairs def zrangebyscore(key, min, max, options = {}) args = [] with_scores = options[:with_scores] || options[:withscores] if with_scores args << "WITHSCORES" block = _floatify_pairs end limit = options[:limit] args.concat(["LIMIT"] + limit) if limit synchronize do |client| client.call([:zrangebyscore, key, min, max] + args, &block) end end # Return a range of members in a sorted set, by score, with scores ordered # from high to low. # # @example Retrieve members with score `< 100` and `>= 5` # redis.zrevrangebyscore("zset", "(100", "5") # # => ["b", "a"] # @example Retrieve the first 2 members with score `<= 0` # redis.zrevrangebyscore("zset", "0", "-inf", :limit => [0, 2]) # # => ["b", "a"] # @example Retrieve members and their scores with scores `> 5` # redis.zrevrangebyscore("zset", "+inf", "(5", :with_scores => true) # # => [["b", 64.0], ["a", 32.0]] # # @see #zrangebyscore def zrevrangebyscore(key, max, min, options = {}) args = [] with_scores = options[:with_scores] || options[:withscores] if with_scores args << ["WITHSCORES"] block = _floatify_pairs end limit = options[:limit] args.concat(["LIMIT"] + limit) if limit synchronize do |client| client.call([:zrevrangebyscore, key, max, min] + args, &block) end end # Remove all members in a sorted set within the given scores. # # @example Remove members with score `>= 5` and `< 100` # redis.zremrangebyscore("zset", "5", "(100") # # => 2 # @example Remove members with scores `> 5` # redis.zremrangebyscore("zset", "(5", "+inf") # # => 2 # # @param [String] key # @param [String] min # - inclusive minimum score is specified verbatim # - exclusive minimum score is specified by prefixing `(` # @param [String] max # - inclusive maximum score is specified verbatim # - exclusive maximum score is specified by prefixing `(` # @return [Fixnum] number of members that were removed def zremrangebyscore(key, min, max) synchronize do |client| client.call([:zremrangebyscore, key, min, max]) end end # Count the members in a sorted set with scores within the given values. # # @example Count members with score `>= 5` and `< 100` # redis.zcount("zset", "5", "(100") # # => 2 # @example Count members with scores `> 5` # redis.zcount("zset", "(5", "+inf") # # => 2 # # @param [String] key # @param [String] min # - inclusive minimum score is specified verbatim # - exclusive minimum score is specified by prefixing `(` # @param [String] max # - inclusive maximum score is specified verbatim # - exclusive maximum score is specified by prefixing `(` # @return [Fixnum] number of members in within the specified range def zcount(key, min, max) synchronize do |client| client.call([:zcount, key, min, max]) end end # Intersect multiple sorted sets and store the resulting sorted set in a new # key. # # @example Compute the intersection of `2*zsetA` with `1*zsetB`, summing their scores # redis.zinterstore("zsetC", ["zsetA", "zsetB"], :weights => [2.0, 1.0], :aggregate => "sum") # # => 4 # # @param [String] destination destination key # @param [Array] keys source keys # @param [Hash] options # - `:weights => [Float, Float, ...]`: weights to associate with source # sorted sets # - `:aggregate => String`: aggregate function to use (sum, min, max, ...) # @return [Fixnum] number of elements in the resulting sorted set def zinterstore(destination, keys, options = {}) args = [] weights = options[:weights] args.concat(["WEIGHTS"] + weights) if weights aggregate = options[:aggregate] args.concat(["AGGREGATE", aggregate]) if aggregate synchronize do |client| client.call([:zinterstore, destination, keys.size] + keys + args) end end # Add multiple sorted sets and store the resulting sorted set in a new key. # # @example Compute the union of `2*zsetA` with `1*zsetB`, summing their scores # redis.zunionstore("zsetC", ["zsetA", "zsetB"], :weights => [2.0, 1.0], :aggregate => "sum") # # => 8 # # @param [String] destination destination key # @param [Array] keys source keys # @param [Hash] options # - `:weights => [Float, Float, ...]`: weights to associate with source # sorted sets # - `:aggregate => String`: aggregate function to use (sum, min, max, ...) # @return [Fixnum] number of elements in the resulting sorted set def zunionstore(destination, keys, options = {}) args = [] weights = options[:weights] args.concat(["WEIGHTS"] + weights) if weights aggregate = options[:aggregate] args.concat(["AGGREGATE", aggregate]) if aggregate synchronize do |client| client.call([:zunionstore, destination, keys.size] + keys + args) end end # Get the number of fields in a hash. # # @param [String] key # @return [Fixnum] number of fields in the hash def hlen(key) synchronize do |client| client.call([:hlen, key]) end end # Set the string value of a hash field. # # @param [String] key # @param [String] field # @param [String] value # @return [Boolean] whether or not the field was **added** to the hash def hset(key, field, value) synchronize do |client| client.call([:hset, key, field, value], &_boolify) end end # Set the value of a hash field, only if the field does not exist. # # @param [String] key # @param [String] field # @param [String] value # @return [Boolean] whether or not the field was **added** to the hash def hsetnx(key, field, value) synchronize do |client| client.call([:hsetnx, key, field, value], &_boolify) end end # Set one or more hash values. # # @example # redis.hmset("hash", "f1", "v1", "f2", "v2") # # => "OK" # # @param [String] key # @param [Array] attrs array of fields and values # @return [String] `"OK"` # # @see #mapped_hmset def hmset(key, *attrs) synchronize do |client| client.call([:hmset, key] + attrs) end end # Set one or more hash values. # # @example # redis.mapped_hmset("hash", { "f1" => "v1", "f2" => "v2" }) # # => "OK" # # @param [String] key # @param [Hash] hash a non-empty hash with fields mapping to values # @return [String] `"OK"` # # @see #hmset def mapped_hmset(key, hash) hmset(key, hash.to_a.flatten) end # Get the value of a hash field. # # @param [String] key # @param [String] field # @return [String] def hget(key, field) synchronize do |client| client.call([:hget, key, field]) end end # Get the values of all the given hash fields. # # @example # redis.hmget("hash", "f1", "f2") # # => ["v1", "v2"] # # @param [String] key # @param [Array] fields array of fields # @return [Array] an array of values for the specified fields # # @see #mapped_hmget def hmget(key, *fields, &blk) synchronize do |client| client.call([:hmget, key] + fields, &blk) end end # Get the values of all the given hash fields. # # @example # redis.mapped_hmget("hash", "f1", "f2") # # => { "f1" => "v1", "f2" => "v2" } # # @param [String] key # @param [Array] fields array of fields # @return [Hash] a hash mapping the specified fields to their values # # @see #hmget def mapped_hmget(key, *fields) hmget(key, *fields) do |reply| if reply.kind_of?(Array) Hash[fields.zip(reply)] else reply end end end # Delete one or more hash fields. # # @param [String] key # @param [String, Array] field # @return [Fixnum] the number of fields that were removed from the hash def hdel(key, field) synchronize do |client| client.call([:hdel, key, field]) end end # Determine if a hash field exists. # # @param [String] key # @param [String] field # @return [Boolean] whether or not the field exists in the hash def hexists(key, field) synchronize do |client| client.call([:hexists, key, field], &_boolify) end end # Increment the integer value of a hash field by the given integer number. # # @param [String] key # @param [String] field # @param [Fixnum] increment # @return [Fixnum] value of the field after incrementing it def hincrby(key, field, increment) synchronize do |client| client.call([:hincrby, key, field, increment]) end end # Increment the numeric value of a hash field by the given float number. # # @param [String] key # @param [String] field # @param [Float] increment # @return [Float] value of the field after incrementing it def hincrbyfloat(key, field, increment) synchronize do |client| client.call([:hincrbyfloat, key, field, increment], &_floatify) end end # Get all the fields in a hash. # # @param [String] key # @return [Array] def hkeys(key) synchronize do |client| client.call([:hkeys, key]) end end # Get all the values in a hash. # # @param [String] key # @return [Array] def hvals(key) synchronize do |client| client.call([:hvals, key]) end end # Get all the fields and values in a hash. # # @param [String] key # @return [Hash] def hgetall(key) synchronize do |client| client.call([:hgetall, key], &_hashify) end end # Post a message to a channel. def publish(channel, message) synchronize do |client| client.call([:publish, channel, message]) end end def subscribed? synchronize do |client| client.kind_of? SubscribedClient end end # Listen for messages published to the given channels. def subscribe(*channels, &block) synchronize do |client| _subscription(:subscribe, channels, block) end end # Stop listening for messages posted to the given channels. def unsubscribe(*channels) synchronize do |client| raise RuntimeError, "Can't unsubscribe if not subscribed." unless subscribed? client.unsubscribe(*channels) end end # Listen for messages published to channels matching the given patterns. def psubscribe(*channels, &block) synchronize do |client| _subscription(:psubscribe, channels, block) end end # Stop listening for messages posted to channels matching the given patterns. def punsubscribe(*channels) synchronize do |client| raise RuntimeError, "Can't unsubscribe if not subscribed." unless subscribed? client.punsubscribe(*channels) end end # Inspect the state of the Pub/Sub subsystem. # Possible subcommands: channels, numsub, numpat. def pubsub(subcommand, *args) synchronize do |client| client.call([:pubsub, subcommand] + args) end end # Watch the given keys to determine execution of the MULTI/EXEC block. # # Using a block is optional, but is necessary for thread-safety. # # An `#unwatch` is automatically issued if an exception is raised within the # block that is a subclass of StandardError and is not a ConnectionError. # # @example With a block # redis.watch("key") do # if redis.get("key") == "some value" # redis.multi do |multi| # multi.set("key", "other value") # multi.incr("counter") # end # else # redis.unwatch # end # end # # => ["OK", 6] # # @example Without a block # redis.watch("key") # # => "OK" # # @param [String, Array] keys one or more keys to watch # @return [Object] if using a block, returns the return value of the block # @return [String] if not using a block, returns `OK` # # @see #unwatch # @see #multi def watch(*keys) synchronize do |client| res = client.call([:watch] + keys) if block_given? begin yield(self) rescue ConnectionError raise rescue StandardError unwatch raise end else res end end end # Forget about all watched keys. # # @return [String] `OK` # # @see #watch # @see #multi def unwatch synchronize do |client| client.call([:unwatch]) end end def pipelined synchronize do |client| begin original, @client = @client, Pipeline.new yield(self) original.call_pipeline(@client) ensure @client = original end end end # Mark the start of a transaction block. # # Passing a block is optional. # # @example With a block # redis.multi do |multi| # multi.set("key", "value") # multi.incr("counter") # end # => ["OK", 6] # # @example Without a block # redis.multi # # => "OK" # redis.set("key", "value") # # => "QUEUED" # redis.incr("counter") # # => "QUEUED" # redis.exec # # => ["OK", 6] # # @yield [multi] the commands that are called inside this block are cached # and written to the server upon returning from it # @yieldparam [Redis] multi `self` # # @return [String, Array<...>] # - when a block is not given, `OK` # - when a block is given, an array with replies # # @see #watch # @see #unwatch def multi synchronize do |client| if !block_given? client.call([:multi]) else begin pipeline = Pipeline::Multi.new original, @client = @client, pipeline yield(self) original.call_pipeline(pipeline) ensure @client = original end end end end # Execute all commands issued after MULTI. # # Only call this method when `#multi` was called **without** a block. # # @return [nil, Array<...>] # - when commands were not executed, `nil` # - when commands were executed, an array with their replies # # @see #multi # @see #discard def exec synchronize do |client| client.call([:exec]) end end # Discard all commands issued after MULTI. # # Only call this method when `#multi` was called **without** a block. # # @return [String] `"OK"` # # @see #multi # @see #exec def discard synchronize do |client| client.call([:discard]) end end # Control remote script registry. # # @example Load a script # sha = redis.script(:load, "return 1") # # => # @example Check if a script exists # redis.script(:exists, sha) # # => true # @example Check if multiple scripts exist # redis.script(:exists, [sha, other_sha]) # # => [true, false] # @example Flush the script registry # redis.script(:flush) # # => "OK" # @example Kill a running script # redis.script(:kill) # # => "OK" # # @param [String] subcommand e.g. `exists`, `flush`, `load`, `kill` # @param [Array] args depends on subcommand # @return [String, Boolean, Array, ...] depends on subcommand # # @see #eval # @see #evalsha def script(subcommand, *args) subcommand = subcommand.to_s.downcase if subcommand == "exists" synchronize do |client| arg = args.first client.call([:script, :exists, arg]) do |reply| reply = reply.map { |r| _boolify.call(r) } if arg.is_a?(Array) reply else reply.first end end end else synchronize do |client| client.call([:script, subcommand] + args) end end end def _eval(cmd, args) script = args.shift options = args.pop if args.last.is_a?(Hash) options ||= {} keys = args.shift || options[:keys] || [] argv = args.shift || options[:argv] || [] synchronize do |client| client.call([cmd, script, keys.length] + keys + argv) end end # Evaluate Lua script. # # @example EVAL without KEYS nor ARGV # redis.eval("return 1") # # => 1 # @example EVAL with KEYS and ARGV as array arguments # redis.eval("return { KEYS, ARGV }", ["k1", "k2"], ["a1", "a2"]) # # => [["k1", "k2"], ["a1", "a2"]] # @example EVAL with KEYS and ARGV in a hash argument # redis.eval("return { KEYS, ARGV }", :keys => ["k1", "k2"], :argv => ["a1", "a2"]) # # => [["k1", "k2"], ["a1", "a2"]] # # @param [Array] keys optional array with keys to pass to the script # @param [Array] argv optional array with arguments to pass to the script # @param [Hash] options # - `:keys => Array`: optional array with keys to pass to the script # - `:argv => Array`: optional array with arguments to pass to the script # @return depends on the script # # @see #script # @see #evalsha def eval(*args) _eval(:eval, args) end # Evaluate Lua script by its SHA. # # @example EVALSHA without KEYS nor ARGV # redis.evalsha(sha) # # => # @example EVALSHA with KEYS and ARGV as array arguments # redis.evalsha(sha, ["k1", "k2"], ["a1", "a2"]) # # => # @example EVALSHA with KEYS and ARGV in a hash argument # redis.evalsha(sha, :keys => ["k1", "k2"], :argv => ["a1", "a2"]) # # => # # @param [Array] keys optional array with keys to pass to the script # @param [Array] argv optional array with arguments to pass to the script # @param [Hash] options # - `:keys => Array`: optional array with keys to pass to the script # - `:argv => Array`: optional array with arguments to pass to the script # @return depends on the script # # @see #script # @see #eval def evalsha(*args) _eval(:evalsha, args) end def _scan(command, cursor, args, options = {}, &block) # SSCAN/ZSCAN/HSCAN already prepend the key to +args+. args << cursor if match = options[:match] args.concat(["MATCH", match]) end if count = options[:count] args.concat(["COUNT", count]) end synchronize do |client| client.call([command] + args, &block) end end # Scan the keyspace # # @example Retrieve the first batch of keys # redis.scan(0) # # => ["4", ["key:21", "key:47", "key:42"]] # @example Retrieve a batch of keys matching a pattern # redis.scan(4, :match => "key:1?") # # => ["92", ["key:13", "key:18"]] # # @param [String, Integer] cursor the cursor of the iteration # @param [Hash] options # - `:match => String`: only return keys matching the pattern # - `:count => Integer`: return count keys at most per iteration # # @return [String, Array] the next cursor and all found keys def scan(cursor, options={}) _scan(:scan, cursor, [], options) end # Scan the keyspace # # @example Retrieve all of the keys (with possible duplicates) # redis.scan_each.to_a # # => ["key:21", "key:47", "key:42"] # @example Execute block for each key matching a pattern # redis.scan_each(:match => "key:1?") {|key| puts key} # # => key:13 # # => key:18 # # @param [Hash] options # - `:match => String`: only return keys matching the pattern # - `:count => Integer`: return count keys at most per iteration # # @return [Enumerator] an enumerator for all found keys def scan_each(options={}, &block) return to_enum(:scan_each, options) unless block_given? cursor = 0 loop do cursor, keys = scan(cursor, options) keys.each(&block) break if cursor == "0" end end # Scan a hash # # @example Retrieve the first batch of key/value pairs in a hash # redis.hscan("hash", 0) # # @param [String, Integer] cursor the cursor of the iteration # @param [Hash] options # - `:match => String`: only return keys matching the pattern # - `:count => Integer`: return count keys at most per iteration # # @return [String, Array<[String, String]>] the next cursor and all found keys def hscan(key, cursor, options={}) _scan(:hscan, cursor, [key], options) do |reply| [reply[0], _pairify(reply[1])] end end # Scan a hash # # @example Retrieve all of the key/value pairs in a hash # redis.hscan_each("hash").to_a # # => [["key70", "70"], ["key80", "80"]] # # @param [Hash] options # - `:match => String`: only return keys matching the pattern # - `:count => Integer`: return count keys at most per iteration # # @return [Enumerator] an enumerator for all found keys def hscan_each(key, options={}, &block) return to_enum(:hscan_each, key, options) unless block_given? cursor = 0 loop do cursor, values = hscan(key, cursor, options) values.each(&block) break if cursor == "0" end end # Scan a sorted set # # @example Retrieve the first batch of key/value pairs in a hash # redis.zscan("zset", 0) # # @param [String, Integer] cursor the cursor of the iteration # @param [Hash] options # - `:match => String`: only return keys matching the pattern # - `:count => Integer`: return count keys at most per iteration # # @return [String, Array<[String, Float]>] the next cursor and all found # members and scores def zscan(key, cursor, options={}) _scan(:zscan, cursor, [key], options) do |reply| [reply[0], _floatify_pairs.call(reply[1])] end end # Scan a sorted set # # @example Retrieve all of the members/scores in a sorted set # redis.zscan_each("zset").to_a # # => [["key70", "70"], ["key80", "80"]] # # @param [Hash] options # - `:match => String`: only return keys matching the pattern # - `:count => Integer`: return count keys at most per iteration # # @return [Enumerator] an enumerator for all found scores and members def zscan_each(key, options={}, &block) return to_enum(:zscan_each, key, options) unless block_given? cursor = 0 loop do cursor, values = zscan(key, cursor, options) values.each(&block) break if cursor == "0" end end # Scan a set # # @example Retrieve the first batch of keys in a set # redis.sscan("set", 0) # # @param [String, Integer] cursor the cursor of the iteration # @param [Hash] options # - `:match => String`: only return keys matching the pattern # - `:count => Integer`: return count keys at most per iteration # # @return [String, Array] the next cursor and all found members def sscan(key, cursor, options={}) _scan(:sscan, cursor, [key], options) end # Scan a set # # @example Retrieve all of the keys in a set # redis.sscan_each("set").to_a # # => ["key1", "key2", "key3"] # # @param [Hash] options # - `:match => String`: only return keys matching the pattern # - `:count => Integer`: return count keys at most per iteration # # @return [Enumerator] an enumerator for all keys in the set def sscan_each(key, options={}, &block) return to_enum(:sscan_each, key, options) unless block_given? cursor = 0 loop do cursor, keys = sscan(key, cursor, options) keys.each(&block) break if cursor == "0" end end # Add one or more members to a HyperLogLog structure. # # @param [String] key # @param [String, Array] member one member, or array of members # @return [Boolean] true if at least 1 HyperLogLog internal register was altered. false otherwise. def pfadd(key, member) synchronize do |client| client.call([:pfadd, key, member], &_boolify) end end # Get the approximate cardinality of members added to HyperLogLog structure. # # If called with multiple keys, returns the approximate cardinality of the # union of the HyperLogLogs contained in the keys. # # @param [String, Array] keys # @return [Fixnum] def pfcount(*keys) synchronize do |client| client.call([:pfcount] + keys) end end # Merge multiple HyperLogLog values into an unique value that will approximate the cardinality of the union of # the observed Sets of the source HyperLogLog structures. # # @param [String] dest_key destination key # @param [String, Array] source_key source key, or array of keys # @return [Boolean] def pfmerge(dest_key, *source_key) synchronize do |client| client.call([:pfmerge, dest_key, *source_key], &_boolify_set) end end # Interact with the sentinel command (masters, master, slaves, failover) # # @param [String] subcommand e.g. `masters`, `master`, `slaves` # @param [Array] args depends on subcommand # @return [Array, Hash, String] depends on subcommand def sentinel(subcommand, *args) subcommand = subcommand.to_s.downcase synchronize do |client| client.call([:sentinel, subcommand] + args) do |reply| case subcommand when "get-master-addr-by-name" reply else if reply.kind_of?(Array) if reply[0].kind_of?(Array) reply.map(&_hashify) else _hashify.call(reply) end else reply end end end end end def id @original_client.id end def inspect "#" end def dup self.class.new(@options) end def method_missing(command, *args) synchronize do |client| client.call([command] + args) end end private # Commands returning 1 for true and 0 for false may be executed in a pipeline # where the method call will return nil. Propagate the nil instead of falsely # returning false. def _boolify lambda { |value| value == 1 if value } end def _boolify_set lambda { |value| if value && "OK" == value true else false end } end def _hashify lambda { |array| hash = Hash.new array.each_slice(2) do |field, value| hash[field] = value end hash } end def _floatify lambda { |str| return unless str if (inf = str.match(/^(-)?inf/i)) (inf[1] ? -1.0 : 1.0) / 0.0 else Float(str) end } end def _floatify_pairs lambda { |array| return unless array array.each_slice(2).map do |member, score| [member, _floatify.call(score)] end } end def _pairify(array) array.each_slice(2).to_a end def _identity lambda { |value| value } end def _subscription(method, channels, block) return @client.call([method] + channels) if subscribed? begin original, @client = @client, SubscribedClient.new(@client) @client.send(method, *channels, &block) ensure @client = original end end end require "redis/version" require "redis/connection" require "redis/client" require "redis/pipeline" require "redis/subscribe" redis-3.2.2/lib/redis/0000755000004100000410000000000012637240307014534 5ustar www-datawww-dataredis-3.2.2/lib/redis/distributed.rb0000644000004100000410000005412312637240307017410 0ustar www-datawww-datarequire "redis/hash_ring" class Redis class Distributed class CannotDistribute < RuntimeError def initialize(command) @command = command end def message "#{@command.to_s.upcase} cannot be used in Redis::Distributed because the keys involved need to be on the same server or because we cannot guarantee that the operation will be atomic." end end attr_reader :ring def initialize(node_configs, options = {}) @tag = options[:tag] || /^\{(.+?)\}/ @ring = options[:ring] || HashRing.new @node_configs = node_configs.dup @default_options = options.dup node_configs.each { |node_config| add_node(node_config) } @subscribed_node = nil end def node_for(key) @ring.get_node(key_tag(key.to_s) || key.to_s) end def nodes @ring.nodes end def add_node(options) options = { :url => options } if options.is_a?(String) options = @default_options.merge(options) @ring.add_node Redis.new( options ) end # Change the selected database for the current connection. def select(db) on_each_node :select, db end # Ping the server. def ping on_each_node :ping end # Echo the given string. def echo(value) on_each_node :echo, value end # Close the connection. def quit on_each_node :quit end # Asynchronously save the dataset to disk. def bgsave on_each_node :bgsave end # Return the number of keys in the selected database. def dbsize on_each_node :dbsize end # Remove all keys from all databases. def flushall on_each_node :flushall end # Remove all keys from the current database. def flushdb on_each_node :flushdb end # Get information and statistics about the server. def info(cmd = nil) on_each_node :info, cmd end # Get the UNIX time stamp of the last successful save to disk. def lastsave on_each_node :lastsave end # Listen for all requests received by the server in real time. def monitor raise NotImplementedError end # Synchronously save the dataset to disk. def save on_each_node :save end # Get server time: an UNIX timestamp and the elapsed microseconds in the current second. def time on_each_node :time end # Remove the expiration from a key. def persist(key) node_for(key).persist(key) end # Set a key's time to live in seconds. def expire(key, seconds) node_for(key).expire(key, seconds) end # Set the expiration for a key as a UNIX timestamp. def expireat(key, unix_time) node_for(key).expireat(key, unix_time) end # Get the time to live (in seconds) for a key. def ttl(key) node_for(key).ttl(key) end # Set a key's time to live in milliseconds. def pexpire(key, milliseconds) node_for(key).pexpire(key, milliseconds) end # Set the expiration for a key as number of milliseconds from UNIX Epoch. def pexpireat(key, ms_unix_time) node_for(key).pexpireat(key, ms_unix_time) end # Get the time to live (in milliseconds) for a key. def pttl(key) node_for(key).pttl(key) end # Return a serialized version of the value stored at a key. def dump(key) node_for(key).dump(key) end # Create a key using the serialized value, previously obtained using DUMP. def restore(key, ttl, serialized_value) node_for(key).restore(key, ttl, serialized_value) end # Transfer a key from the connected instance to another instance. def migrate(key, options) raise CannotDistribute, :migrate end # Delete a key. def del(*args) keys_per_node = args.group_by { |key| node_for(key) } keys_per_node.inject(0) do |sum, (node, keys)| sum + node.del(*keys) end end # Determine if a key exists. def exists(key) node_for(key).exists(key) end # Find all keys matching the given pattern. def keys(glob = "*") on_each_node(:keys, glob).flatten end # Move a key to another database. def move(key, db) node_for(key).move(key, db) end # Return a random key from the keyspace. def randomkey raise CannotDistribute, :randomkey end # Rename a key. def rename(old_name, new_name) ensure_same_node(:rename, [old_name, new_name]) do |node| node.rename(old_name, new_name) end end # Rename a key, only if the new key does not exist. def renamenx(old_name, new_name) ensure_same_node(:renamenx, [old_name, new_name]) do |node| node.renamenx(old_name, new_name) end end # Sort the elements in a list, set or sorted set. def sort(key, options = {}) keys = [key, options[:by], options[:store], *Array(options[:get])].compact ensure_same_node(:sort, keys) do |node| node.sort(key, options) end end # Determine the type stored at key. def type(key) node_for(key).type(key) end # Decrement the integer value of a key by one. def decr(key) node_for(key).decr(key) end # Decrement the integer value of a key by the given number. def decrby(key, decrement) node_for(key).decrby(key, decrement) end # Increment the integer value of a key by one. def incr(key) node_for(key).incr(key) end # Increment the integer value of a key by the given integer number. def incrby(key, increment) node_for(key).incrby(key, increment) end # Increment the numeric value of a key by the given float number. def incrbyfloat(key, increment) node_for(key).incrbyfloat(key, increment) end # Set the string value of a key. def set(key, value, options = {}) node_for(key).set(key, value, options) end # Set the time to live in seconds of a key. def setex(key, ttl, value) node_for(key).setex(key, ttl, value) end # Set the time to live in milliseconds of a key. def psetex(key, ttl, value) node_for(key).psetex(key, ttl, value) end # Set the value of a key, only if the key does not exist. def setnx(key, value) node_for(key).setnx(key, value) end # Set multiple keys to multiple values. def mset(*args) raise CannotDistribute, :mset end def mapped_mset(hash) raise CannotDistribute, :mapped_mset end # Set multiple keys to multiple values, only if none of the keys exist. def msetnx(*args) raise CannotDistribute, :msetnx end def mapped_msetnx(hash) raise CannotDistribute, :mapped_msetnx end # Get the value of a key. def get(key) node_for(key).get(key) end # Get the values of all the given keys. def mget(*keys) raise CannotDistribute, :mget end def mapped_mget(*keys) raise CannotDistribute, :mapped_mget end # Overwrite part of a string at key starting at the specified offset. def setrange(key, offset, value) node_for(key).setrange(key, offset, value) end # Get a substring of the string stored at a key. def getrange(key, start, stop) node_for(key).getrange(key, start, stop) end # Sets or clears the bit at offset in the string value stored at key. def setbit(key, offset, value) node_for(key).setbit(key, offset, value) end # Returns the bit value at offset in the string value stored at key. def getbit(key, offset) node_for(key).getbit(key, offset) end # Append a value to a key. def append(key, value) node_for(key).append(key, value) end # Count the number of set bits in a range of the string value stored at key. def bitcount(key, start = 0, stop = -1) node_for(key).bitcount(key, start, stop) end # Perform a bitwise operation between strings and store the resulting string in a key. def bitop(operation, destkey, *keys) ensure_same_node(:bitop, [destkey] + keys) do |node| node.bitop(operation, destkey, *keys) end end # Return the position of the first bit set to 1 or 0 in a string. def bitpos(key, bit, start=nil, stop=nil) node_for(key).bitpos(key, bit, start, stop) end # Set the string value of a key and return its old value. def getset(key, value) node_for(key).getset(key, value) end # Get the length of the value stored in a key. def strlen(key) node_for(key).strlen(key) end def [](key) get(key) end def []=(key,value) set(key, value) end # Get the length of a list. def llen(key) node_for(key).llen(key) end # Prepend one or more values to a list. def lpush(key, value) node_for(key).lpush(key, value) end # Prepend a value to a list, only if the list exists. def lpushx(key, value) node_for(key).lpushx(key, value) end # Append one or more values to a list. def rpush(key, value) node_for(key).rpush(key, value) end # Append a value to a list, only if the list exists. def rpushx(key, value) node_for(key).rpushx(key, value) end # Remove and get the first element in a list. def lpop(key) node_for(key).lpop(key) end # Remove and get the last element in a list. def rpop(key) node_for(key).rpop(key) end # Remove the last element in a list, append it to another list and return # it. def rpoplpush(source, destination) ensure_same_node(:rpoplpush, [source, destination]) do |node| node.rpoplpush(source, destination) end end def _bpop(cmd, args) options = {} case args.last when Hash options = args.pop when Integer # Issue deprecation notice in obnoxious mode... options[:timeout] = args.pop end if args.size > 1 # Issue deprecation notice in obnoxious mode... end keys = args.flatten ensure_same_node(cmd, keys) do |node| node.__send__(cmd, keys, options) end end # Remove and get the first element in a list, or block until one is # available. def blpop(*args) _bpop(:blpop, args) end # Remove and get the last element in a list, or block until one is # available. def brpop(*args) _bpop(:brpop, args) end # Pop a value from a list, push it to another list and return it; or block # until one is available. def brpoplpush(source, destination, options = {}) case options when Integer # Issue deprecation notice in obnoxious mode... options = { :timeout => options } end ensure_same_node(:brpoplpush, [source, destination]) do |node| node.brpoplpush(source, destination, options) end end # Get an element from a list by its index. def lindex(key, index) node_for(key).lindex(key, index) end # Insert an element before or after another element in a list. def linsert(key, where, pivot, value) node_for(key).linsert(key, where, pivot, value) end # Get a range of elements from a list. def lrange(key, start, stop) node_for(key).lrange(key, start, stop) end # Remove elements from a list. def lrem(key, count, value) node_for(key).lrem(key, count, value) end # Set the value of an element in a list by its index. def lset(key, index, value) node_for(key).lset(key, index, value) end # Trim a list to the specified range. def ltrim(key, start, stop) node_for(key).ltrim(key, start, stop) end # Get the number of members in a set. def scard(key) node_for(key).scard(key) end # Add one or more members to a set. def sadd(key, member) node_for(key).sadd(key, member) end # Remove one or more members from a set. def srem(key, member) node_for(key).srem(key, member) end # Remove and return a random member from a set. def spop(key) node_for(key).spop(key) end # Get a random member from a set. def srandmember(key, count = nil) node_for(key).srandmember(key, count) end # Move a member from one set to another. def smove(source, destination, member) ensure_same_node(:smove, [source, destination]) do |node| node.smove(source, destination, member) end end # Determine if a given value is a member of a set. def sismember(key, member) node_for(key).sismember(key, member) end # Get all the members in a set. def smembers(key) node_for(key).smembers(key) end # Subtract multiple sets. def sdiff(*keys) ensure_same_node(:sdiff, keys) do |node| node.sdiff(*keys) end end # Subtract multiple sets and store the resulting set in a key. def sdiffstore(destination, *keys) ensure_same_node(:sdiffstore, [destination] + keys) do |node| node.sdiffstore(destination, *keys) end end # Intersect multiple sets. def sinter(*keys) ensure_same_node(:sinter, keys) do |node| node.sinter(*keys) end end # Intersect multiple sets and store the resulting set in a key. def sinterstore(destination, *keys) ensure_same_node(:sinterstore, [destination] + keys) do |node| node.sinterstore(destination, *keys) end end # Add multiple sets. def sunion(*keys) ensure_same_node(:sunion, keys) do |node| node.sunion(*keys) end end # Add multiple sets and store the resulting set in a key. def sunionstore(destination, *keys) ensure_same_node(:sunionstore, [destination] + keys) do |node| node.sunionstore(destination, *keys) end end # Get the number of members in a sorted set. def zcard(key) node_for(key).zcard(key) end # Add one or more members to a sorted set, or update the score for members # that already exist. def zadd(key, *args) node_for(key).zadd(key, *args) end # Increment the score of a member in a sorted set. def zincrby(key, increment, member) node_for(key).zincrby(key, increment, member) end # Remove one or more members from a sorted set. def zrem(key, member) node_for(key).zrem(key, member) end # Get the score associated with the given member in a sorted set. def zscore(key, member) node_for(key).zscore(key, member) end # Return a range of members in a sorted set, by index. def zrange(key, start, stop, options = {}) node_for(key).zrange(key, start, stop, options) end # Return a range of members in a sorted set, by index, with scores ordered # from high to low. def zrevrange(key, start, stop, options = {}) node_for(key).zrevrange(key, start, stop, options) end # Determine the index of a member in a sorted set. def zrank(key, member) node_for(key).zrank(key, member) end # Determine the index of a member in a sorted set, with scores ordered from # high to low. def zrevrank(key, member) node_for(key).zrevrank(key, member) end # Remove all members in a sorted set within the given indexes. def zremrangebyrank(key, start, stop) node_for(key).zremrangebyrank(key, start, stop) end # Return a range of members in a sorted set, by score. def zrangebyscore(key, min, max, options = {}) node_for(key).zrangebyscore(key, min, max, options) end # Return a range of members in a sorted set, by score, with scores ordered # from high to low. def zrevrangebyscore(key, max, min, options = {}) node_for(key).zrevrangebyscore(key, max, min, options) end # Remove all members in a sorted set within the given scores. def zremrangebyscore(key, min, max) node_for(key).zremrangebyscore(key, min, max) end # Get the number of members in a particular score range. def zcount(key, min, max) node_for(key).zcount(key, min, max) end # Intersect multiple sorted sets and store the resulting sorted set in a new # key. def zinterstore(destination, keys, options = {}) ensure_same_node(:zinterstore, [destination] + keys) do |node| node.zinterstore(destination, keys, options) end end # Add multiple sorted sets and store the resulting sorted set in a new key. def zunionstore(destination, keys, options = {}) ensure_same_node(:zunionstore, [destination] + keys) do |node| node.zunionstore(destination, keys, options) end end # Get the number of fields in a hash. def hlen(key) node_for(key).hlen(key) end # Set the string value of a hash field. def hset(key, field, value) node_for(key).hset(key, field, value) end # Set the value of a hash field, only if the field does not exist. def hsetnx(key, field, value) node_for(key).hsetnx(key, field, value) end # Set multiple hash fields to multiple values. def hmset(key, *attrs) node_for(key).hmset(key, *attrs) end def mapped_hmset(key, hash) node_for(key).hmset(key, *hash.to_a.flatten) end # Get the value of a hash field. def hget(key, field) node_for(key).hget(key, field) end # Get the values of all the given hash fields. def hmget(key, *fields) node_for(key).hmget(key, *fields) end def mapped_hmget(key, *fields) Hash[*fields.zip(hmget(key, *fields)).flatten] end # Delete one or more hash fields. def hdel(key, field) node_for(key).hdel(key, field) end # Determine if a hash field exists. def hexists(key, field) node_for(key).hexists(key, field) end # Increment the integer value of a hash field by the given integer number. def hincrby(key, field, increment) node_for(key).hincrby(key, field, increment) end # Increment the numeric value of a hash field by the given float number. def hincrbyfloat(key, field, increment) node_for(key).hincrbyfloat(key, field, increment) end # Get all the fields in a hash. def hkeys(key) node_for(key).hkeys(key) end # Get all the values in a hash. def hvals(key) node_for(key).hvals(key) end # Get all the fields and values in a hash. def hgetall(key) node_for(key).hgetall(key) end # Post a message to a channel. def publish(channel, message) node_for(channel).publish(channel, message) end def subscribed? !! @subscribed_node end # Listen for messages published to the given channels. def subscribe(channel, *channels, &block) if channels.empty? @subscribed_node = node_for(channel) @subscribed_node.subscribe(channel, &block) else ensure_same_node(:subscribe, [channel] + channels) do |node| @subscribed_node = node node.subscribe(channel, *channels, &block) end end end # Stop listening for messages posted to the given channels. def unsubscribe(*channels) raise RuntimeError, "Can't unsubscribe if not subscribed." unless subscribed? @subscribed_node.unsubscribe(*channels) end # Listen for messages published to channels matching the given patterns. def psubscribe(*channels, &block) raise NotImplementedError end # Stop listening for messages posted to channels matching the given # patterns. def punsubscribe(*channels) raise NotImplementedError end # Watch the given keys to determine execution of the MULTI/EXEC block. def watch(*keys) raise CannotDistribute, :watch end # Forget about all watched keys. def unwatch raise CannotDistribute, :unwatch end def pipelined raise CannotDistribute, :pipelined end # Mark the start of a transaction block. def multi raise CannotDistribute, :multi end # Execute all commands issued after MULTI. def exec raise CannotDistribute, :exec end # Discard all commands issued after MULTI. def discard raise CannotDistribute, :discard end # Control remote script registry. def script(subcommand, *args) on_each_node(:script, subcommand, *args) end # Add one or more members to a HyperLogLog structure. def pfadd(key, member) node_for(key).pfadd(key, member) end # Get the approximate cardinality of members added to HyperLogLog structure. def pfcount(*keys) ensure_same_node(:pfcount, keys.flatten(1)) do |node| node.pfcount(keys) end end # Merge multiple HyperLogLog values into an unique value that will approximate the cardinality of the union of # the observed Sets of the source HyperLogLog structures. def pfmerge(dest_key, *source_key) ensure_same_node(:pfmerge, [dest_key, *source_key]) do |node| node.pfmerge(dest_key, *source_key) end end def _eval(cmd, args) script = args.shift options = args.pop if args.last.is_a?(Hash) options ||= {} keys = args.shift || options[:keys] || [] argv = args.shift || options[:argv] || [] ensure_same_node(cmd, keys) do |node| node.send(cmd, script, keys, argv) end end # Evaluate Lua script. def eval(*args) _eval(:eval, args) end # Evaluate Lua script by its SHA. def evalsha(*args) _eval(:evalsha, args) end def inspect "#" end def dup self.class.new(@node_configs, @default_options) end protected def on_each_node(command, *args) nodes.map do |node| node.send(command, *args) end end def node_index_for(key) nodes.index(node_for(key)) end def key_tag(key) key.to_s[@tag, 1] if @tag end def ensure_same_node(command, keys) all = true tags = keys.map do |key| tag = key_tag(key) all = false unless tag tag end if (all && tags.uniq.size != 1) || (!all && keys.uniq.size != 1) # Not 1 unique tag or not 1 unique key raise CannotDistribute, command end yield(node_for(keys.first)) end end end redis-3.2.2/lib/redis/hash_ring.rb0000644000004100000410000000676112637240307017035 0ustar www-datawww-datarequire 'zlib' class Redis class HashRing POINTS_PER_SERVER = 160 # this is the default in libmemcached attr_reader :ring, :sorted_keys, :replicas, :nodes # nodes is a list of objects that have a proper to_s representation. # replicas indicates how many virtual points should be used pr. node, # replicas are required to improve the distribution. def initialize(nodes=[], replicas=POINTS_PER_SERVER) @replicas = replicas @ring = {} @nodes = [] @sorted_keys = [] nodes.each do |node| add_node(node) end end # Adds a `node` to the hash ring (including a number of replicas). def add_node(node) @nodes << node @replicas.times do |i| key = Zlib.crc32("#{node.id}:#{i}") raise "Node ID collision" if @ring.has_key?(key) @ring[key] = node @sorted_keys << key end @sorted_keys.sort! end def remove_node(node) @nodes.reject!{|n| n.id == node.id} @replicas.times do |i| key = Zlib.crc32("#{node.id}:#{i}") @ring.delete(key) @sorted_keys.reject! {|k| k == key} end end # get the node in the hash ring for this key def get_node(key) get_node_pos(key)[0] end def get_node_pos(key) return [nil,nil] if @ring.size == 0 crc = Zlib.crc32(key) idx = HashRing.binary_search(@sorted_keys, crc) return [@ring[@sorted_keys[idx]], idx] end def iter_nodes(key) return [nil,nil] if @ring.size == 0 _, pos = get_node_pos(key) @ring.size.times do |n| yield @ring[@sorted_keys[(pos+n) % @ring.size]] end end class << self # gem install RubyInline to use this code # Native extension to perform the binary search within the hashring. # There's a pure ruby version below so this is purely optional # for performance. In testing 20k gets and sets, the native # binary search shaved about 12% off the runtime (9sec -> 8sec). begin require 'inline' inline do |builder| builder.c <<-EOM int binary_search(VALUE ary, unsigned int r) { int upper = RARRAY_LEN(ary) - 1; int lower = 0; int idx = 0; while (lower <= upper) { idx = (lower + upper) / 2; VALUE continuumValue = RARRAY_PTR(ary)[idx]; unsigned int l = NUM2UINT(continuumValue); if (l == r) { return idx; } else if (l > r) { upper = idx - 1; } else { lower = idx + 1; } } if (upper < 0) { upper = RARRAY_LEN(ary) - 1; } return upper; } EOM end rescue Exception # Find the closest index in HashRing with value <= the given value def binary_search(ary, value, &block) upper = ary.size - 1 lower = 0 idx = 0 while(lower <= upper) do idx = (lower + upper) / 2 comp = ary[idx] <=> value if comp == 0 return idx elsif comp > 0 upper = idx - 1 else lower = idx + 1 end end if upper < 0 upper = ary.size - 1 end return upper end end end end end redis-3.2.2/lib/redis/subscribe.rb0000644000004100000410000000327312637240307017047 0ustar www-datawww-dataclass Redis class SubscribedClient def initialize(client) @client = client end def call(command) @client.process([command]) end def subscribe(*channels, &block) subscription("subscribe", "unsubscribe", channels, block) end def psubscribe(*channels, &block) subscription("psubscribe", "punsubscribe", channels, block) end def unsubscribe(*channels) call([:unsubscribe, *channels]) end def punsubscribe(*channels) call([:punsubscribe, *channels]) end protected def subscription(start, stop, channels, block) sub = Subscription.new(&block) unsubscribed = false begin @client.call_loop([start, *channels]) do |line| type, *rest = line sub.callbacks[type].call(*rest) unsubscribed = type == stop && rest.last == 0 break if unsubscribed end ensure # No need to unsubscribe here. The real client closes the connection # whenever an exception is raised (see #ensure_connected). end end end class Subscription attr :callbacks def initialize @callbacks = Hash.new do |hash, key| hash[key] = lambda { |*_| } end yield(self) end def subscribe(&block) @callbacks["subscribe"] = block end def unsubscribe(&block) @callbacks["unsubscribe"] = block end def message(&block) @callbacks["message"] = block end def psubscribe(&block) @callbacks["psubscribe"] = block end def punsubscribe(&block) @callbacks["punsubscribe"] = block end def pmessage(&block) @callbacks["pmessage"] = block end end end redis-3.2.2/lib/redis/connection.rb0000644000004100000410000000101112637240307017211 0ustar www-datawww-datarequire "redis/connection/registry" # If a connection driver was required before this file, the array # Redis::Connection.drivers will contain one or more classes. The last driver # in this array will be used as default driver. If this array is empty, we load # the plain Ruby driver as our default. Another driver can be required at a # later point in time, causing it to be the last element of the #drivers array # and therefore be chosen by default. require "redis/connection/ruby" if Redis::Connection.drivers.empty?redis-3.2.2/lib/redis/pipeline.rb0000644000004100000410000000601112637240307016664 0ustar www-datawww-dataclass Redis unless defined?(::BasicObject) class BasicObject instance_methods.each { |meth| undef_method(meth) unless meth =~ /\A(__|instance_eval)/ } end end class Pipeline attr_accessor :db attr :futures def initialize @with_reconnect = true @shutdown = false @futures = [] end def with_reconnect? @with_reconnect end def without_reconnect? !@with_reconnect end def shutdown? @shutdown end def call(command, &block) # A pipeline that contains a shutdown should not raise ECONNRESET when # the connection is gone. @shutdown = true if command.first == :shutdown future = Future.new(command, block) @futures << future future end def call_pipeline(pipeline) @shutdown = true if pipeline.shutdown? @futures.concat(pipeline.futures) @db = pipeline.db nil end def commands @futures.map { |f| f._command } end def with_reconnect(val=true) @with_reconnect = false unless val yield end def without_reconnect(&blk) with_reconnect(false, &blk) end def finish(replies, &blk) if blk futures.each_with_index.map do |future, i| future._set(blk.call(replies[i])) end else futures.each_with_index.map do |future, i| future._set(replies[i]) end end end class Multi < self def finish(replies) exec = replies.last return if exec.nil? # The transaction failed because of WATCH. # EXEC command failed. raise exec if exec.is_a?(CommandError) if exec.size < futures.size # Some command wasn't recognized by Redis. raise replies.detect { |r| r.is_a?(CommandError) } end super(exec) do |reply| # Because an EXEC returns nested replies, hiredis won't be able to # convert an error reply to a CommandError instance itself. This is # specific to MULTI/EXEC, so we solve this here. reply.is_a?(::RuntimeError) ? CommandError.new(reply.message) : reply end end def commands [[:multi]] + super + [[:exec]] end end end class FutureNotReady < RuntimeError def initialize super("Value will be available once the pipeline executes.") end end class Future < BasicObject FutureNotReady = ::Redis::FutureNotReady.new def initialize(command, transformation) @command = command @transformation = transformation @object = FutureNotReady end def inspect "" end def _set(object) @object = @transformation ? @transformation.call(object) : object value end def _command @command end def value ::Kernel.raise(@object) if @object.kind_of?(::RuntimeError) @object end def is_a?(other) self.class.ancestors.include?(other) end def class Future end end end redis-3.2.2/lib/redis/errors.rb0000644000004100000410000000210312637240307016371 0ustar www-datawww-dataclass Redis # Base error for all redis-rb errors. class BaseError < RuntimeError end # Raised by the connection when a protocol error occurs. class ProtocolError < BaseError def initialize(reply_type) super(<<-EOS.gsub(/(?:^|\n)\s*/, " ")) Got '#{reply_type}' as initial reply byte. If you're in a forking environment, such as Unicorn, you need to connect to Redis after forking. EOS end end # Raised by the client when command execution returns an error reply. class CommandError < BaseError end # Base error for connection related errors. class BaseConnectionError < BaseError end # Raised when connection to a Redis server cannot be made. class CannotConnectError < BaseConnectionError end # Raised when connection to a Redis server is lost. class ConnectionError < BaseConnectionError end # Raised when performing I/O times out. class TimeoutError < BaseConnectionError end # Raised when the connection was inherited by a child process. class InheritedError < BaseConnectionError end end redis-3.2.2/lib/redis/client.rb0000644000004100000410000003371412637240307016347 0ustar www-datawww-datarequire "redis/errors" require "socket" require "cgi" class Redis class Client DEFAULTS = { :url => lambda { ENV["REDIS_URL"] }, :scheme => "redis", :host => "127.0.0.1", :port => 6379, :path => nil, :timeout => 5.0, :connect_timeout => 5.0, :password => nil, :db => 0, :driver => nil, :id => nil, :tcp_keepalive => 0, :reconnect_attempts => 1, :inherit_socket => false } def options Marshal.load(Marshal.dump(@options)) end def scheme @options[:scheme] end def host @options[:host] end def port @options[:port] end def path @options[:path] end def timeout @options[:timeout] end def password @options[:password] end def db @options[:db] end def db=(db) @options[:db] = db.to_i end def driver @options[:driver] end def inherit_socket? @options[:inherit_socket] end attr_accessor :logger attr_reader :connection attr_reader :command_map def initialize(options = {}) @options = _parse_options(options) @reconnect = true @logger = @options[:logger] @connection = nil @command_map = {} @pending_reads = 0 if options.include?(:sentinels) @connector = Connector::Sentinel.new(@options) else @connector = Connector.new(@options) end end def connect @pid = Process.pid # Don't try to reconnect when the connection is fresh with_reconnect(false) do establish_connection call [:auth, password] if password call [:select, db] if db != 0 call [:client, :setname, @options[:id]] if @options[:id] @connector.check(self) end self end def id @options[:id] || "redis://#{location}/#{db}" end def location path || "#{host}:#{port}" end def call(command, &block) reply = process([command]) { read } raise reply if reply.is_a?(CommandError) if block block.call(reply) else reply end end def call_loop(command) error = nil result = without_socket_timeout do process([command]) do loop do reply = read if reply.is_a?(CommandError) error = reply break else yield reply end end end end # Raise error when previous block broke out of the loop. raise error if error # Result is set to the value that the provided block used to break. result end def call_pipeline(pipeline) with_reconnect pipeline.with_reconnect? do begin pipeline.finish(call_pipelined(pipeline.commands)).tap do self.db = pipeline.db if pipeline.db end rescue ConnectionError => e return nil if pipeline.shutdown? # Assume the pipeline was sent in one piece, but execution of # SHUTDOWN caused none of the replies for commands that were executed # prior to it from coming back around. raise e end end end def call_pipelined(commands) return [] if commands.empty? # The method #ensure_connected (called from #process) reconnects once on # I/O errors. To make an effort in making sure that commands are not # executed more than once, only allow reconnection before the first reply # has been read. When an error occurs after the first reply has been # read, retrying would re-execute the entire pipeline, thus re-issuing # already successfully executed commands. To circumvent this, don't retry # after the first reply has been read successfully. result = Array.new(commands.size) reconnect = @reconnect begin process(commands) do result[0] = read @reconnect = false (commands.size - 1).times do |i| result[i + 1] = read end end ensure @reconnect = reconnect end result end def call_with_timeout(command, timeout, &blk) with_socket_timeout(timeout) do call(command, &blk) end rescue ConnectionError retry end def call_without_timeout(command, &blk) call_with_timeout(command, 0, &blk) end def process(commands) logging(commands) do ensure_connected do commands.each do |command| if command_map[command.first] command = command.dup command[0] = command_map[command.first] end write(command) end yield if block_given? end end end def connected? !! (connection && connection.connected?) end def disconnect connection.disconnect if connected? end def reconnect disconnect connect end def io yield rescue TimeoutError => e1 # Add a message to the exception without destroying the original stack e2 = TimeoutError.new("Connection timed out") e2.set_backtrace(e1.backtrace) raise e2 rescue Errno::ECONNRESET, Errno::EPIPE, Errno::ECONNABORTED, Errno::EBADF, Errno::EINVAL => e raise ConnectionError, "Connection lost (%s)" % [e.class.name.split("::").last] end def read io do value = connection.read @pending_reads -= 1 value end end def write(command) io do @pending_reads += 1 connection.write(command) end end def with_socket_timeout(timeout) connect unless connected? begin connection.timeout = timeout yield ensure connection.timeout = self.timeout if connected? end end def without_socket_timeout(&blk) with_socket_timeout(0, &blk) end def with_reconnect(val=true) begin original, @reconnect = @reconnect, val yield ensure @reconnect = original end end def without_reconnect(&blk) with_reconnect(false, &blk) end protected def logging(commands) return yield unless @logger && @logger.debug? begin commands.each do |name, *args| logged_args = args.map do |a| case when a.respond_to?(:inspect) then a.inspect when a.respond_to?(:to_s) then a.to_s else # handle poorly-behaved descendants of BasicObject klass = a.instance_exec { (class << self; self end).superclass } "\#<#{klass}:#{a.__id__}>" end end @logger.debug("[Redis] command=#{name.to_s.upcase} args=#{logged_args.join(' ')}") end t1 = Time.now yield ensure @logger.debug("[Redis] call_time=%0.2f ms" % ((Time.now - t1) * 1000)) if t1 end end def establish_connection server = @connector.resolve.dup @options[:host] = server[:host] @options[:port] = Integer(server[:port]) if server.include?(:port) @connection = @options[:driver].connect(@options) @pending_reads = 0 rescue TimeoutError, Errno::ECONNREFUSED, Errno::EHOSTDOWN, Errno::EHOSTUNREACH, Errno::ENETUNREACH, Errno::ETIMEDOUT raise CannotConnectError, "Error connecting to Redis on #{location} (#{$!.class})" end def ensure_connected disconnect if @pending_reads > 0 attempts = 0 begin attempts += 1 if connected? unless inherit_socket? || Process.pid == @pid raise InheritedError, "Tried to use a connection from a child process without reconnecting. " + "You need to reconnect to Redis after forking " + "or set :inherit_socket to true." end else connect end yield rescue BaseConnectionError disconnect if attempts <= @options[:reconnect_attempts] && @reconnect retry else raise end rescue Exception disconnect raise end end def _parse_options(options) return options if options[:_parsed] defaults = DEFAULTS.dup options = options.dup defaults.keys.each do |key| # Fill in defaults if needed if defaults[key].respond_to?(:call) defaults[key] = defaults[key].call end # Symbolize only keys that are needed options[key] = options[key.to_s] if options.has_key?(key.to_s) end url = options[:url] || defaults[:url] # Override defaults from URL if given if url require "uri" uri = URI(url) if uri.scheme == "unix" defaults[:path] = uri.path elsif uri.scheme == "redis" defaults[:scheme] = uri.scheme defaults[:host] = uri.host if uri.host defaults[:port] = uri.port if uri.port defaults[:password] = CGI.unescape(uri.password) if uri.password defaults[:db] = uri.path[1..-1].to_i if uri.path defaults[:role] = :master else raise ArgumentError, "invalid uri scheme '#{uri.scheme}'" end end # Use default when option is not specified or nil defaults.keys.each do |key| options[key] = defaults[key] if options[key].nil? end if options[:path] # Unix socket options[:scheme] = "unix" options.delete(:host) options.delete(:port) else # TCP socket options[:host] = options[:host].to_s options[:port] = options[:port].to_i end options[:timeout] = options[:timeout].to_f options[:connect_timeout] = if options[:connect_timeout] options[:connect_timeout].to_f else options[:timeout] end options[:db] = options[:db].to_i options[:driver] = _parse_driver(options[:driver]) || Connection.drivers.last case options[:tcp_keepalive] when Hash [:time, :intvl, :probes].each do |key| unless options[:tcp_keepalive][key].is_a?(Fixnum) raise "Expected the #{key.inspect} key in :tcp_keepalive to be a Fixnum" end end when Fixnum if options[:tcp_keepalive] >= 60 options[:tcp_keepalive] = {:time => options[:tcp_keepalive] - 20, :intvl => 10, :probes => 2} elsif options[:tcp_keepalive] >= 30 options[:tcp_keepalive] = {:time => options[:tcp_keepalive] - 10, :intvl => 5, :probes => 2} elsif options[:tcp_keepalive] >= 5 options[:tcp_keepalive] = {:time => options[:tcp_keepalive] - 2, :intvl => 2, :probes => 1} end end options[:_parsed] = true options end def _parse_driver(driver) driver = driver.to_s if driver.is_a?(Symbol) if driver.kind_of?(String) begin require "redis/connection/#{driver}" driver = Connection.const_get(driver.capitalize) rescue LoadError, NameError raise RuntimeError, "Cannot load driver #{driver.inspect}" end end driver end class Connector def initialize(options) @options = options.dup end def resolve @options end def check(client) end class Sentinel < Connector def initialize(options) super(options) @options[:password] = DEFAULTS.fetch(:password) @options[:db] = DEFAULTS.fetch(:db) @sentinels = @options.delete(:sentinels).dup @role = @options.fetch(:role, "master").to_s @master = @options[:host] end def check(client) # Check the instance is really of the role we are looking for. # We can't assume the command is supported since it was introduced # recently and this client should work with old stuff. begin role = client.call([:role])[0] rescue Redis::CommandError # Assume the test is passed if we can't get a reply from ROLE... role = @role end if role != @role client.disconnect raise ConnectionError, "Instance role mismatch. Expected #{@role}, got #{role}." end end def resolve result = case @role when "master" resolve_master when "slave" resolve_slave else raise ArgumentError, "Unknown instance role #{@role}" end result || (raise ConnectionError, "Unable to fetch #{@role} via Sentinel.") end def sentinel_detect @sentinels.each do |sentinel| client = Client.new(@options.merge({ :host => sentinel[:host], :port => sentinel[:port], :reconnect_attempts => 0, })) begin if result = yield(client) # This sentinel responded. Make sure we ask it first next time. @sentinels.delete(sentinel) @sentinels.unshift(sentinel) return result end rescue BaseConnectionError ensure client.disconnect end end raise CannotConnectError, "No sentinels available." end def resolve_master sentinel_detect do |client| if reply = client.call(["sentinel", "get-master-addr-by-name", @master]) {:host => reply[0], :port => reply[1]} end end end def resolve_slave sentinel_detect do |client| if reply = client.call(["sentinel", "slaves", @master]) slave = Hash[*reply.sample] {:host => slave.fetch("ip"), :port => slave.fetch("port")} end end end end end end end redis-3.2.2/lib/redis/version.rb0000644000004100000410000000004412637240307016544 0ustar www-datawww-dataclass Redis VERSION = "3.2.2" end redis-3.2.2/lib/redis/connection/0000755000004100000410000000000012637240307016673 5ustar www-datawww-dataredis-3.2.2/lib/redis/connection/ruby.rb0000644000004100000410000002025612637240307020206 0ustar www-datawww-datarequire "redis/connection/registry" require "redis/connection/command_helper" require "redis/errors" require "socket" class Redis module Connection module SocketMixin CRLF = "\r\n".freeze def initialize(*args) super(*args) @timeout = nil @buffer = "" end def timeout=(timeout) if timeout && timeout > 0 @timeout = timeout else @timeout = nil end end def read(nbytes) result = @buffer.slice!(0, nbytes) while result.bytesize < nbytes result << _read_from_socket(nbytes - result.bytesize) end result end def gets crlf = nil while (crlf = @buffer.index(CRLF)) == nil @buffer << _read_from_socket(1024) end @buffer.slice!(0, crlf + CRLF.bytesize) end def _read_from_socket(nbytes) begin read_nonblock(nbytes) rescue Errno::EWOULDBLOCK, Errno::EAGAIN if IO.select([self], nil, nil, @timeout) retry else raise Redis::TimeoutError end end rescue EOFError raise Errno::ECONNRESET end end if defined?(RUBY_ENGINE) && RUBY_ENGINE == "jruby" require "timeout" class TCPSocket < ::TCPSocket include SocketMixin def self.connect(host, port, timeout) Timeout.timeout(timeout) do sock = new(host, port) sock end rescue Timeout::Error raise TimeoutError end end if defined?(::UNIXSocket) class UNIXSocket < ::UNIXSocket include SocketMixin def self.connect(path, timeout) Timeout.timeout(timeout) do sock = new(path) sock end rescue Timeout::Error raise TimeoutError end # JRuby raises Errno::EAGAIN on #read_nonblock even when IO.select # says it is readable (1.6.6, in both 1.8 and 1.9 mode). # Use the blocking #readpartial method instead. def _read_from_socket(nbytes) readpartial(nbytes) rescue EOFError raise Errno::ECONNRESET end end end else class TCPSocket < ::Socket include SocketMixin def self.connect_addrinfo(ai, port, timeout) sock = new(::Socket.const_get(ai[0]), Socket::SOCK_STREAM, 0) sockaddr = ::Socket.pack_sockaddr_in(port, ai[3]) begin sock.connect_nonblock(sockaddr) rescue Errno::EINPROGRESS if IO.select(nil, [sock], nil, timeout) == nil raise TimeoutError end begin sock.connect_nonblock(sockaddr) rescue Errno::EISCONN end end sock end def self.connect(host, port, timeout) # Don't pass AI_ADDRCONFIG as flag to getaddrinfo(3) # # From the man page for getaddrinfo(3): # # If hints.ai_flags includes the AI_ADDRCONFIG flag, then IPv4 # addresses are returned in the list pointed to by res only if the # local system has at least one IPv4 address configured, and IPv6 # addresses are returned only if the local system has at least one # IPv6 address configured. The loopback address is not considered # for this case as valid as a configured address. # # We do want the IPv6 loopback address to be returned if applicable, # even if it is the only configured IPv6 address on the machine. # Also see: https://github.com/redis/redis-rb/pull/394. addrinfo = ::Socket.getaddrinfo(host, nil, Socket::AF_UNSPEC, Socket::SOCK_STREAM) # From the man page for getaddrinfo(3): # # Normally, the application should try using the addresses in the # order in which they are returned. The sorting function used # within getaddrinfo() is defined in RFC 3484 [...]. # addrinfo.each_with_index do |ai, i| begin return connect_addrinfo(ai, port, timeout) rescue SystemCallError # Raise if this was our last attempt. raise if addrinfo.length == i+1 end end end end class UNIXSocket < ::Socket include SocketMixin def self.connect(path, timeout) sock = new(::Socket::AF_UNIX, Socket::SOCK_STREAM, 0) sockaddr = ::Socket.pack_sockaddr_un(path) begin sock.connect_nonblock(sockaddr) rescue Errno::EINPROGRESS if IO.select(nil, [sock], nil, timeout) == nil raise TimeoutError end begin sock.connect_nonblock(sockaddr) rescue Errno::EISCONN end end sock end end end class Ruby include Redis::Connection::CommandHelper MINUS = "-".freeze PLUS = "+".freeze COLON = ":".freeze DOLLAR = "$".freeze ASTERISK = "*".freeze def self.connect(config) if config[:scheme] == "unix" sock = UNIXSocket.connect(config[:path], config[:connect_timeout]) else sock = TCPSocket.connect(config[:host], config[:port], config[:connect_timeout]) end instance = new(sock) instance.timeout = config[:timeout] instance.set_tcp_keepalive config[:tcp_keepalive] instance end if [:SOL_SOCKET, :SO_KEEPALIVE, :SOL_TCP, :TCP_KEEPIDLE, :TCP_KEEPINTVL, :TCP_KEEPCNT].all?{|c| Socket.const_defined? c} def set_tcp_keepalive(keepalive) return unless keepalive.is_a?(Hash) @sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_KEEPALIVE, true) @sock.setsockopt(Socket::SOL_TCP, Socket::TCP_KEEPIDLE, keepalive[:time]) @sock.setsockopt(Socket::SOL_TCP, Socket::TCP_KEEPINTVL, keepalive[:intvl]) @sock.setsockopt(Socket::SOL_TCP, Socket::TCP_KEEPCNT, keepalive[:probes]) end def get_tcp_keepalive { :time => @sock.getsockopt(Socket::SOL_TCP, Socket::TCP_KEEPIDLE).int, :intvl => @sock.getsockopt(Socket::SOL_TCP, Socket::TCP_KEEPINTVL).int, :probes => @sock.getsockopt(Socket::SOL_TCP, Socket::TCP_KEEPCNT).int, } end else def set_tcp_keepalive(keepalive) end def get_tcp_keepalive { } end end def initialize(sock) @sock = sock end def connected? !! @sock end def disconnect @sock.close rescue ensure @sock = nil end def timeout=(timeout) if @sock.respond_to?(:timeout=) @sock.timeout = timeout end end def write(command) @sock.write(build_command(command)) end def read line = @sock.gets reply_type = line.slice!(0, 1) format_reply(reply_type, line) rescue Errno::EAGAIN raise TimeoutError end def format_reply(reply_type, line) case reply_type when MINUS then format_error_reply(line) when PLUS then format_status_reply(line) when COLON then format_integer_reply(line) when DOLLAR then format_bulk_reply(line) when ASTERISK then format_multi_bulk_reply(line) else raise ProtocolError.new(reply_type) end end def format_error_reply(line) CommandError.new(line.strip) end def format_status_reply(line) line.strip end def format_integer_reply(line) line.to_i end def format_bulk_reply(line) bulklen = line.to_i return if bulklen == -1 reply = encode(@sock.read(bulklen)) @sock.read(2) # Discard CRLF. reply end def format_multi_bulk_reply(line) n = line.to_i return if n == -1 Array.new(n) { read } end end end end Redis::Connection.drivers << Redis::Connection::Ruby redis-3.2.2/lib/redis/connection/synchrony.rb0000644000004100000410000000526512637240307021264 0ustar www-datawww-datarequire "redis/connection/command_helper" require "redis/connection/registry" require "redis/errors" require "em-synchrony" require "hiredis/reader" class Redis module Connection class RedisClient < EventMachine::Connection include EventMachine::Deferrable def post_init @req = nil @connected = false @reader = ::Hiredis::Reader.new end def connection_completed @connected = true succeed end def connected? @connected end def receive_data(data) @reader.feed(data) loop do begin reply = @reader.gets rescue RuntimeError => err @req.fail [:error, ProtocolError.new(err.message)] break end break if reply == false reply = CommandError.new(reply.message) if reply.is_a?(RuntimeError) @req.succeed [:reply, reply] end end def read @req = EventMachine::DefaultDeferrable.new EventMachine::Synchrony.sync @req end def send(data) callback { send_data data } end def unbind @connected = false if @req @req.fail [:error, Errno::ECONNRESET] @req = nil else fail end end end class Synchrony include Redis::Connection::CommandHelper def self.connect(config) if config[:scheme] == "unix" conn = EventMachine.connect_unix_domain(config[:path], RedisClient) else conn = EventMachine.connect(config[:host], config[:port], RedisClient) do |c| c.pending_connect_timeout = [config[:connect_timeout], 0.1].max end end fiber = Fiber.current conn.callback { fiber.resume } conn.errback { fiber.resume :refused } raise Errno::ECONNREFUSED if Fiber.yield == :refused instance = new(conn) instance.timeout = config[:timeout] instance end def initialize(connection) @connection = connection end def connected? @connection && @connection.connected? end def timeout=(timeout) @timeout = timeout end def disconnect @connection.close_connection @connection = nil end def write(command) @connection.send(build_command(command)) end def read type, payload = @connection.read if type == :reply payload elsif type == :error raise payload else raise "Unknown type #{type.inspect}" end end end end end Redis::Connection.drivers << Redis::Connection::Synchrony redis-3.2.2/lib/redis/connection/command_helper.rb0000644000004100000410000000155312637240307022201 0ustar www-datawww-dataclass Redis module Connection module CommandHelper COMMAND_DELIMITER = "\r\n" def build_command(args) command = [nil] args.each do |i| if i.is_a? Array i.each do |j| j = j.to_s command << "$#{j.bytesize}" command << j end else i = i.to_s command << "$#{i.bytesize}" command << i end end command[0] = "*#{(command.length - 1) / 2}" # Trailing delimiter command << "" command.join(COMMAND_DELIMITER) end protected if defined?(Encoding::default_external) def encode(string) string.force_encoding(Encoding::default_external) end else def encode(string) string end end end end end redis-3.2.2/lib/redis/connection/registry.rb0000644000004100000410000000054412637240307021073 0ustar www-datawww-dataclass Redis module Connection # Store a list of loaded connection drivers in the Connection module. # Redis::Client uses the last required driver by default, and will be aware # of the loaded connection drivers if the user chooses to override the # default connection driver. def self.drivers @drivers ||= [] end end end redis-3.2.2/lib/redis/connection/hiredis.rb0000644000004100000410000000304112637240307020645 0ustar www-datawww-datarequire "redis/connection/registry" require "redis/errors" require "hiredis/connection" require "timeout" class Redis module Connection class Hiredis def self.connect(config) connection = ::Hiredis::Connection.new connect_timeout = (config.fetch(:connect_timeout, 0) * 1_000_000).to_i if config[:scheme] == "unix" connection.connect_unix(config[:path], connect_timeout) else connection.connect(config[:host], config[:port], connect_timeout) end instance = new(connection) instance.timeout = config[:timeout] instance rescue Errno::ETIMEDOUT raise TimeoutError end def initialize(connection) @connection = connection end def connected? @connection && @connection.connected? end def timeout=(timeout) # Hiredis works with microsecond timeouts @connection.timeout = Integer(timeout * 1_000_000) end def disconnect @connection.disconnect @connection = nil end def write(command) @connection.write(command.flatten(1)) rescue Errno::EAGAIN raise TimeoutError end def read reply = @connection.read reply = CommandError.new(reply.message) if reply.is_a?(RuntimeError) reply rescue Errno::EAGAIN raise TimeoutError rescue RuntimeError => err raise ProtocolError.new(err.message) end end end end Redis::Connection.drivers << Redis::Connection::Hiredis redis-3.2.2/metadata.yml0000644000004100000410000001635612637240307015176 0ustar www-datawww-data--- !ruby/object:Gem::Specification name: redis version: !ruby/object:Gem::Version version: 3.2.2 platform: ruby authors: - Ezra Zygmuntowicz - Taylor Weibley - Matthew Clark - Brian McKinney - Salvatore Sanfilippo - Luca Guidi - Michel Martens - Damian Janowski - Pieter Noordhuis autorequire: bindir: bin cert_chain: [] date: 2015-11-16 00:00:00.000000000 Z dependencies: - !ruby/object:Gem::Dependency name: rake requirement: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: '0' type: :development prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: '0' - !ruby/object:Gem::Dependency name: test-unit requirement: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: '0' type: :development prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: '0' description: |2 A Ruby client that tries to match Redis' API one-to-one, while still providing an idiomatic interface. It features thread-safety, client-side sharding, pipelining, and an obsession for performance. email: - redis-db@googlegroups.com executables: [] extensions: [] extra_rdoc_files: [] files: - ".gitignore" - ".travis.yml" - ".travis/Gemfile" - ".yardopts" - CHANGELOG.md - Gemfile - LICENSE - README.md - Rakefile - benchmarking/logging.rb - benchmarking/pipeline.rb - benchmarking/speed.rb - benchmarking/suite.rb - benchmarking/worker.rb - examples/basic.rb - examples/consistency.rb - examples/dist_redis.rb - examples/incr-decr.rb - examples/list.rb - examples/pubsub.rb - examples/sentinel.rb - examples/sentinel/sentinel.conf - examples/sentinel/start - examples/sets.rb - examples/unicorn/config.ru - examples/unicorn/unicorn.rb - lib/redis.rb - lib/redis/client.rb - lib/redis/connection.rb - lib/redis/connection/command_helper.rb - lib/redis/connection/hiredis.rb - lib/redis/connection/registry.rb - lib/redis/connection/ruby.rb - lib/redis/connection/synchrony.rb - lib/redis/distributed.rb - lib/redis/errors.rb - lib/redis/hash_ring.rb - lib/redis/pipeline.rb - lib/redis/subscribe.rb - lib/redis/version.rb - redis.gemspec - test/bitpos_test.rb - test/blocking_commands_test.rb - test/command_map_test.rb - test/commands_on_hashes_test.rb - test/commands_on_hyper_log_log_test.rb - test/commands_on_lists_test.rb - test/commands_on_sets_test.rb - test/commands_on_sorted_sets_test.rb - test/commands_on_strings_test.rb - test/commands_on_value_types_test.rb - test/connection_handling_test.rb - test/db/.gitkeep - test/distributed_blocking_commands_test.rb - test/distributed_commands_on_hashes_test.rb - test/distributed_commands_on_hyper_log_log_test.rb - test/distributed_commands_on_lists_test.rb - test/distributed_commands_on_sets_test.rb - test/distributed_commands_on_sorted_sets_test.rb - test/distributed_commands_on_strings_test.rb - test/distributed_commands_on_value_types_test.rb - test/distributed_commands_requiring_clustering_test.rb - test/distributed_connection_handling_test.rb - test/distributed_internals_test.rb - test/distributed_key_tags_test.rb - test/distributed_persistence_control_commands_test.rb - test/distributed_publish_subscribe_test.rb - test/distributed_remote_server_control_commands_test.rb - test/distributed_scripting_test.rb - test/distributed_sorting_test.rb - test/distributed_test.rb - test/distributed_transactions_test.rb - test/encoding_test.rb - test/error_replies_test.rb - test/fork_safety_test.rb - test/helper.rb - test/helper_test.rb - test/internals_test.rb - test/lint/blocking_commands.rb - test/lint/hashes.rb - test/lint/hyper_log_log.rb - test/lint/lists.rb - test/lint/sets.rb - test/lint/sorted_sets.rb - test/lint/strings.rb - test/lint/value_types.rb - test/persistence_control_commands_test.rb - test/pipelining_commands_test.rb - test/publish_subscribe_test.rb - test/remote_server_control_commands_test.rb - test/scanning_test.rb - test/scripting_test.rb - test/sentinel_command_test.rb - test/sentinel_test.rb - test/sorting_test.rb - test/support/connection/hiredis.rb - test/support/connection/ruby.rb - test/support/connection/synchrony.rb - test/support/redis_mock.rb - test/support/wire/synchrony.rb - test/support/wire/thread.rb - test/synchrony_driver.rb - test/test.conf.erb - test/thread_safety_test.rb - test/transactions_test.rb - test/unknown_commands_test.rb - test/url_param_test.rb homepage: https://github.com/redis/redis-rb licenses: - MIT metadata: {} post_install_message: rdoc_options: [] require_paths: - lib required_ruby_version: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: '0' required_rubygems_version: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: '0' requirements: [] rubyforge_project: rubygems_version: 2.4.5.1 signing_key: specification_version: 4 summary: A Ruby client library for Redis test_files: - test/bitpos_test.rb - test/blocking_commands_test.rb - test/command_map_test.rb - test/commands_on_hashes_test.rb - test/commands_on_hyper_log_log_test.rb - test/commands_on_lists_test.rb - test/commands_on_sets_test.rb - test/commands_on_sorted_sets_test.rb - test/commands_on_strings_test.rb - test/commands_on_value_types_test.rb - test/connection_handling_test.rb - test/db/.gitkeep - test/distributed_blocking_commands_test.rb - test/distributed_commands_on_hashes_test.rb - test/distributed_commands_on_hyper_log_log_test.rb - test/distributed_commands_on_lists_test.rb - test/distributed_commands_on_sets_test.rb - test/distributed_commands_on_sorted_sets_test.rb - test/distributed_commands_on_strings_test.rb - test/distributed_commands_on_value_types_test.rb - test/distributed_commands_requiring_clustering_test.rb - test/distributed_connection_handling_test.rb - test/distributed_internals_test.rb - test/distributed_key_tags_test.rb - test/distributed_persistence_control_commands_test.rb - test/distributed_publish_subscribe_test.rb - test/distributed_remote_server_control_commands_test.rb - test/distributed_scripting_test.rb - test/distributed_sorting_test.rb - test/distributed_test.rb - test/distributed_transactions_test.rb - test/encoding_test.rb - test/error_replies_test.rb - test/fork_safety_test.rb - test/helper.rb - test/helper_test.rb - test/internals_test.rb - test/lint/blocking_commands.rb - test/lint/hashes.rb - test/lint/hyper_log_log.rb - test/lint/lists.rb - test/lint/sets.rb - test/lint/sorted_sets.rb - test/lint/strings.rb - test/lint/value_types.rb - test/persistence_control_commands_test.rb - test/pipelining_commands_test.rb - test/publish_subscribe_test.rb - test/remote_server_control_commands_test.rb - test/scanning_test.rb - test/scripting_test.rb - test/sentinel_command_test.rb - test/sentinel_test.rb - test/sorting_test.rb - test/support/connection/hiredis.rb - test/support/connection/ruby.rb - test/support/connection/synchrony.rb - test/support/redis_mock.rb - test/support/wire/synchrony.rb - test/support/wire/thread.rb - test/synchrony_driver.rb - test/test.conf.erb - test/thread_safety_test.rb - test/transactions_test.rb - test/unknown_commands_test.rb - test/url_param_test.rb redis-3.2.2/test/0000755000004100000410000000000012637240307013637 5ustar www-datawww-dataredis-3.2.2/test/distributed_scripting_test.rb0000644000004100000410000000567312637240307021642 0ustar www-datawww-data# encoding: UTF-8 require File.expand_path("helper", File.dirname(__FILE__)) class TestDistributedScripting < Test::Unit::TestCase include Helper::Distributed def to_sha(script) r.script(:load, script).first end def test_script_exists target_version "2.5.9" do # 2.6-rc1 a = to_sha("return 1") b = a.succ assert_equal [true], r.script(:exists, a) assert_equal [false], r.script(:exists, b) assert_equal [[true]], r.script(:exists, [a]) assert_equal [[false]], r.script(:exists, [b]) assert_equal [[true, false]], r.script(:exists, [a, b]) end end def test_script_flush target_version "2.5.9" do # 2.6-rc1 sha = to_sha("return 1") assert r.script(:exists, sha).first assert_equal ["OK"], r.script(:flush) assert !r.script(:exists, sha).first end end def test_script_kill target_version "2.5.9" do # 2.6-rc1 redis_mock(:script => lambda { |arg| "+#{arg.upcase}" }) do |redis| assert_equal ["KILL"], redis.script(:kill) end end end def test_eval target_version "2.5.9" do # 2.6-rc1 assert_raises(Redis::Distributed::CannotDistribute) do r.eval("return #KEYS") end assert_raises(Redis::Distributed::CannotDistribute) do r.eval("return KEYS", ["k1", "k2"]) end assert_equal ["k1"], r.eval("return KEYS", ["k1"]) assert_equal ["a1", "a2"], r.eval("return ARGV", ["k1"], ["a1", "a2"]) end end def test_eval_with_options_hash target_version "2.5.9" do # 2.6-rc1 assert_raises(Redis::Distributed::CannotDistribute) do r.eval("return #KEYS", {}) end assert_raises(Redis::Distributed::CannotDistribute) do r.eval("return KEYS", { :keys => ["k1", "k2"] }) end assert_equal ["k1"], r.eval("return KEYS", { :keys => ["k1"] }) assert_equal ["a1", "a2"], r.eval("return ARGV", { :keys => ["k1"], :argv => ["a1", "a2"] }) end end def test_evalsha target_version "2.5.9" do # 2.6-rc1 assert_raises(Redis::Distributed::CannotDistribute) do r.evalsha(to_sha("return #KEYS")) end assert_raises(Redis::Distributed::CannotDistribute) do r.evalsha(to_sha("return KEYS"), ["k1", "k2"]) end assert_equal ["k1"], r.evalsha(to_sha("return KEYS"), ["k1"]) assert_equal ["a1", "a2"], r.evalsha(to_sha("return ARGV"), ["k1"], ["a1", "a2"]) end end def test_evalsha_with_options_hash target_version "2.5.9" do # 2.6-rc1 assert_raises(Redis::Distributed::CannotDistribute) do r.evalsha(to_sha("return #KEYS"), {}) end assert_raises(Redis::Distributed::CannotDistribute) do r.evalsha(to_sha("return KEYS"), { :keys => ["k1", "k2"] }) end assert_equal ["k1"], r.evalsha(to_sha("return KEYS"), { :keys => ["k1"] }) assert_equal ["a1", "a2"], r.evalsha(to_sha("return ARGV"), { :keys => ["k1"], :argv => ["a1", "a2"] }) end end end redis-3.2.2/test/commands_on_strings_test.rb0000644000004100000410000000464612637240307021303 0ustar www-datawww-data# encoding: UTF-8 require File.expand_path("helper", File.dirname(__FILE__)) require "lint/strings" class TestCommandsOnStrings < Test::Unit::TestCase include Helper::Client include Lint::Strings def test_mget r.set("foo", "s1") r.set("bar", "s2") assert_equal ["s1", "s2"] , r.mget("foo", "bar") assert_equal ["s1", "s2", nil], r.mget("foo", "bar", "baz") end def test_mget_mapped r.set("foo", "s1") r.set("bar", "s2") response = r.mapped_mget("foo", "bar") assert_equal "s1", response["foo"] assert_equal "s2", response["bar"] response = r.mapped_mget("foo", "bar", "baz") assert_equal "s1", response["foo"] assert_equal "s2", response["bar"] assert_equal nil , response["baz"] end def test_mapped_mget_in_a_pipeline_returns_hash r.set("foo", "s1") r.set("bar", "s2") result = r.pipelined do r.mapped_mget("foo", "bar") end assert_equal result[0], { "foo" => "s1", "bar" => "s2" } end def test_mset r.mset(:foo, "s1", :bar, "s2") assert_equal "s1", r.get("foo") assert_equal "s2", r.get("bar") end def test_mset_mapped r.mapped_mset(:foo => "s1", :bar => "s2") assert_equal "s1", r.get("foo") assert_equal "s2", r.get("bar") end def test_msetnx r.set("foo", "s1") assert_equal false, r.msetnx(:foo, "s2", :bar, "s3") assert_equal "s1", r.get("foo") assert_equal nil, r.get("bar") r.del("foo") assert_equal true, r.msetnx(:foo, "s2", :bar, "s3") assert_equal "s2", r.get("foo") assert_equal "s3", r.get("bar") end def test_msetnx_mapped r.set("foo", "s1") assert_equal false, r.mapped_msetnx(:foo => "s2", :bar => "s3") assert_equal "s1", r.get("foo") assert_equal nil, r.get("bar") r.del("foo") assert_equal true, r.mapped_msetnx(:foo => "s2", :bar => "s3") assert_equal "s2", r.get("foo") assert_equal "s3", r.get("bar") end def test_bitop try_encoding("UTF-8") do target_version "2.5.10" do r.set("foo", "a") r.set("bar", "b") r.bitop(:and, "foo&bar", "foo", "bar") assert_equal "\x60", r.get("foo&bar") r.bitop(:or, "foo|bar", "foo", "bar") assert_equal "\x63", r.get("foo|bar") r.bitop(:xor, "foo^bar", "foo", "bar") assert_equal "\x03", r.get("foo^bar") r.bitop(:not, "~foo", "foo") assert_equal "\x9E", r.get("~foo") end end end end redis-3.2.2/test/transactions_test.rb0000644000004100000410000001313212637240307017733 0ustar www-datawww-data# encoding: UTF-8 require File.expand_path("helper", File.dirname(__FILE__)) class TestTransactions < Test::Unit::TestCase include Helper::Client def test_multi_discard r.multi assert_equal "QUEUED", r.set("foo", "1") assert_equal "QUEUED", r.get("foo") r.discard assert_equal nil, r.get("foo") end def test_multi_exec_with_a_block r.multi do |multi| multi.set "foo", "s1" end assert_equal "s1", r.get("foo") end def test_multi_exec_with_a_block_doesn_t_return_replies_for_multi_and_exec r1, r2, nothing_else = r.multi do |multi| multi.set "foo", "s1" multi.get "foo" end assert_equal "OK", r1 assert_equal "s1", r2 assert_equal nil, nothing_else end def test_assignment_inside_multi_exec_block r.multi do |m| @first = m.sadd("foo", 1) @second = m.sadd("foo", 1) end assert_equal true, @first.value assert_equal false, @second.value end # Although we could support accessing the values in these futures, # it doesn't make a lot of sense. def test_assignment_inside_multi_exec_block_with_delayed_command_errors assert_raise(Redis::CommandError) do r.multi do |m| @first = m.set("foo", "s1") @second = m.incr("foo") # not an integer @third = m.lpush("foo", "value") # wrong kind of value end end assert_equal "OK", @first.value assert_raise(Redis::CommandError) { @second.value } assert_raise(Redis::FutureNotReady) { @third.value } end def test_assignment_inside_multi_exec_block_with_immediate_command_errors assert_raise(Redis::CommandError) do r.multi do |m| m.doesnt_exist @first = m.sadd("foo", 1) @second = m.sadd("foo", 1) end end assert_raise(Redis::FutureNotReady) { @first.value } assert_raise(Redis::FutureNotReady) { @second.value } end def test_raise_immediate_errors_in_multi_exec assert_raise(RuntimeError) do r.multi do |multi| multi.set "bar", "s2" raise "Some error" multi.set "baz", "s3" end end assert_equal nil, r.get("bar") assert_equal nil, r.get("baz") end def test_transformed_replies_as_return_values_for_multi_exec_block info, _ = r.multi do |m| r.info end assert info.kind_of?(Hash) end def test_transformed_replies_inside_multi_exec_block r.multi do |m| @info = r.info end assert @info.value.kind_of?(Hash) end def test_raise_command_errors_in_multi_exec assert_raise(Redis::CommandError) do r.multi do |m| m.set("foo", "s1") m.incr("foo") # not an integer m.lpush("foo", "value") # wrong kind of value end end assert_equal "s1", r.get("foo") end def test_raise_command_errors_when_accessing_futures_after_multi_exec begin r.multi do |m| m.set("foo", "s1") @counter = m.incr("foo") # not an integer end rescue Exception # Not gonna deal with it end # We should test for Redis::Error here, but hiredis doesn't yet do # custom error classes. err = nil begin @counter.value rescue => err end assert err.kind_of?(RuntimeError) end def test_multi_with_a_block_yielding_the_client r.multi do |multi| multi.set "foo", "s1" end assert_equal "s1", r.get("foo") end def test_raise_command_error_when_exec_fails redis_mock(:exec => lambda { |*_| "-ERROR" }) do |redis| assert_raise(Redis::CommandError) do redis.multi do |m| m.set "foo", "s1" end end end end def test_watch res = r.watch "foo" assert_equal "OK", res end def test_watch_with_an_unmodified_key r.watch "foo" r.multi do |multi| multi.set "foo", "s1" end assert_equal "s1", r.get("foo") end def test_watch_with_an_unmodified_key_passed_as_array r.watch ["foo", "bar"] r.multi do |multi| multi.set "foo", "s1" end assert_equal "s1", r.get("foo") end def test_watch_with_a_modified_key r.watch "foo" r.set "foo", "s1" res = r.multi do |multi| multi.set "foo", "s2" end assert_equal nil, res assert_equal "s1", r.get("foo") end def test_watch_with_a_modified_key_passed_as_array r.watch ["foo", "bar"] r.set "foo", "s1" res = r.multi do |multi| multi.set "foo", "s2" end assert_equal nil, res assert_equal "s1", r.get("foo") end def test_watch_with_a_block_and_an_unmodified_key result = r.watch "foo" do |rd| assert_same r, rd rd.multi do |multi| multi.set "foo", "s1" end end assert_equal ["OK"], result assert_equal "s1", r.get("foo") end def test_watch_with_a_block_and_a_modified_key result = r.watch "foo" do |rd| assert_same r, rd rd.set "foo", "s1" rd.multi do |multi| multi.set "foo", "s2" end end assert_equal nil, result assert_equal "s1", r.get("foo") end def test_watch_with_a_block_that_raises_an_exception r.set("foo", "s1") begin r.watch "foo" do raise "test" end rescue RuntimeError end r.set("foo", "s2") # If the watch was still set from within the block above, this multi/exec # would fail. This proves that raising an exception above unwatches. r.multi do |multi| multi.set "foo", "s3" end assert_equal "s3", r.get("foo") end def test_unwatch_with_a_modified_key r.watch "foo" r.set "foo", "s1" r.unwatch r.multi do |multi| multi.set "foo", "s2" end assert_equal "s2", r.get("foo") end end redis-3.2.2/test/internals_test.rb0000644000004100000410000002463412637240307017233 0ustar www-datawww-data# encoding: UTF-8 require File.expand_path("helper", File.dirname(__FILE__)) class TestInternals < Test::Unit::TestCase include Helper::Client def test_logger r.ping assert log.string["[Redis] command=PING"] assert log.string =~ /\[Redis\] call_time=\d+\.\d+ ms/ end def test_logger_with_pipelining r.pipelined do r.set "foo", "bar" r.get "foo" end assert log.string[" command=SET args=\"foo\" \"bar\""] assert log.string[" command=GET args=\"foo\""] end def test_recovers_from_failed_commands # See https://github.com/redis/redis-rb/issues#issue/28 assert_raise(Redis::CommandError) do r.command_that_doesnt_exist end assert_nothing_raised do r.info end end def test_raises_on_protocol_errors redis_mock(:ping => lambda { |*_| "foo" }) do |redis| assert_raise(Redis::ProtocolError) do redis.ping end end end def test_provides_a_meaningful_inspect assert_equal "#", r.inspect end def test_redis_current assert_equal "127.0.0.1", Redis.current.client.host assert_equal 6379, Redis.current.client.port assert_equal 0, Redis.current.client.db Redis.current = Redis.new(OPTIONS.merge(:port => 6380, :db => 1)) t = Thread.new do assert_equal "127.0.0.1", Redis.current.client.host assert_equal 6380, Redis.current.client.port assert_equal 1, Redis.current.client.db end t.join assert_equal "127.0.0.1", Redis.current.client.host assert_equal 6380, Redis.current.client.port assert_equal 1, Redis.current.client.db end def test_redis_connected? fresh_client = _new_client assert !fresh_client.connected? fresh_client.ping assert fresh_client.connected? fresh_client.quit assert !fresh_client.connected? end def test_default_id_with_host_and_port redis = Redis.new(OPTIONS.merge(:host => "host", :port => "1234", :db => 0)) assert_equal "redis://host:1234/0", redis.client.id end def test_default_id_with_host_and_port_and_explicit_scheme redis = Redis.new(OPTIONS.merge(:host => "host", :port => "1234", :db => 0, :scheme => "foo")) assert_equal "redis://host:1234/0", redis.client.id end def test_default_id_with_path redis = Redis.new(OPTIONS.merge(:path => "/tmp/redis.sock", :db => 0)) assert_equal "redis:///tmp/redis.sock/0", redis.client.id end def test_default_id_with_path_and_explicit_scheme redis = Redis.new(OPTIONS.merge(:path => "/tmp/redis.sock", :db => 0, :scheme => "foo")) assert_equal "redis:///tmp/redis.sock/0", redis.client.id end def test_override_id redis = Redis.new(OPTIONS.merge(:id => "test")) assert_equal redis.client.id, "test" end def test_timeout assert_nothing_raised do Redis.new(OPTIONS.merge(:timeout => 0)) end end def test_id_inside_multi redis = Redis.new(OPTIONS) id = nil redis.multi do id = redis.id end assert_equal id, "redis://127.0.0.1:6381/15" end driver(:ruby) do def test_tcp_keepalive keepalive = {:time => 20, :intvl => 10, :probes => 5} redis = Redis.new(OPTIONS.merge(:tcp_keepalive => keepalive)) redis.ping connection = redis.client.connection actual_keepalive = connection.get_tcp_keepalive [:time, :intvl, :probes].each do |key| if actual_keepalive.has_key?(key) assert_equal actual_keepalive[key], keepalive[key] end end end end def test_time target_version "2.5.4" do # Test that the difference between the time that Ruby reports and the time # that Redis reports is minimal (prevents the test from being racy). rv = r.time redis_usec = rv[0] * 1_000_000 + rv[1] ruby_usec = Integer(Time.now.to_f * 1_000_000) assert 500_000 > (ruby_usec - redis_usec).abs end end def test_connection_timeout opts = OPTIONS.merge(:host => "10.255.255.254", :connect_timeout => 0.1, :timeout => 5.0) start_time = Time.now assert_raise Redis::CannotConnectError do Redis.new(opts).ping end assert (Time.now - start_time) <= opts[:timeout] end def close_on_ping(seq, options = {}) $request = 0 command = lambda do idx = $request $request += 1 rv = "+%d" % idx rv = nil if seq.include?(idx) rv end redis_mock({:ping => command}, {:timeout => 0.1}.merge(options)) do |redis| yield(redis) end end def test_retry_by_default close_on_ping([0]) do |redis| assert_equal "1", redis.ping end end def test_retry_when_wrapped_in_with_reconnect_true close_on_ping([0]) do |redis| redis.with_reconnect(true) do assert_equal "1", redis.ping end end end def test_dont_retry_when_wrapped_in_with_reconnect_false close_on_ping([0]) do |redis| assert_raise Redis::ConnectionError do redis.with_reconnect(false) do redis.ping end end end end def test_dont_retry_when_wrapped_in_without_reconnect close_on_ping([0]) do |redis| assert_raise Redis::ConnectionError do redis.without_reconnect do redis.ping end end end end def test_retry_only_once_when_read_raises_econnreset close_on_ping([0, 1]) do |redis| assert_raise Redis::ConnectionError do redis.ping end assert !redis.client.connected? end end def test_retry_with_custom_reconnect_attempts close_on_ping([0, 1], :reconnect_attempts => 2) do |redis| assert_equal "2", redis.ping end end def test_retry_with_custom_reconnect_attempts_can_still_fail close_on_ping([0, 1, 2], :reconnect_attempts => 2) do |redis| assert_raise Redis::ConnectionError do redis.ping end assert !redis.client.connected? end end def test_don_t_retry_when_second_read_in_pipeline_raises_econnreset close_on_ping([1]) do |redis| assert_raise Redis::ConnectionError do redis.pipelined do redis.ping redis.ping # Second #read times out end end assert !redis.client.connected? end end def close_on_connection(seq) $n = 0 read_command = lambda do |session| Array.new(session.gets[1..-3].to_i) do bytes = session.gets[1..-3].to_i arg = session.read(bytes) session.read(2) # Discard \r\n arg end end handler = lambda do |session| n = $n $n += 1 select = read_command.call(session) if select[0].downcase == "select" session.write("+OK\r\n") else raise "Expected SELECT" end if !seq.include?(n) while read_command.call(session) session.write("+#{n}\r\n") end end end redis_mock_with_handler(handler) do |redis| yield(redis) end end def test_retry_on_write_error_by_default close_on_connection([0]) do |redis| assert_equal "1", redis.client.call(["x" * 128 * 1024]) end end def test_retry_on_write_error_when_wrapped_in_with_reconnect_true close_on_connection([0]) do |redis| redis.with_reconnect(true) do assert_equal "1", redis.client.call(["x" * 128 * 1024]) end end end def test_dont_retry_on_write_error_when_wrapped_in_with_reconnect_false close_on_connection([0]) do |redis| assert_raise Redis::ConnectionError do redis.with_reconnect(false) do redis.client.call(["x" * 128 * 1024]) end end end end def test_dont_retry_on_write_error_when_wrapped_in_without_reconnect close_on_connection([0]) do |redis| assert_raise Redis::ConnectionError do redis.without_reconnect do redis.client.call(["x" * 128 * 1024]) end end end end def test_connecting_to_unix_domain_socket assert_nothing_raised do Redis.new(OPTIONS.merge(:path => "./test/db/redis.sock")).ping end end driver(:ruby, :hiredis) do def test_bubble_timeout_without_retrying serv = TCPServer.new(6380) redis = Redis.new(:port => 6380, :timeout => 0.1) assert_raise(Redis::TimeoutError) do redis.ping end ensure serv.close if serv end end def test_client_options redis = Redis.new(OPTIONS.merge(:host => "host", :port => 1234, :db => 1, :scheme => "foo")) assert_equal "host", redis.client.options[:host] assert_equal 1234, redis.client.options[:port] assert_equal 1, redis.client.options[:db] assert_equal "foo", redis.client.options[:scheme] end def test_does_not_change_self_client_options redis = Redis.new(OPTIONS.merge(:host => "host", :port => 1234, :db => 1, :scheme => "foo")) options = redis.client.options options[:host] << "new_host" options[:scheme] << "bar" options.merge!(:db => 0) assert_equal "host", redis.client.options[:host] assert_equal 1, redis.client.options[:db] assert_equal "foo", redis.client.options[:scheme] end def test_resolves_localhost assert_nothing_raised do Redis.new(OPTIONS.merge(:host => 'localhost')).ping end end class << self def af_family_supported(af) hosts = { Socket::AF_INET => "127.0.0.1", Socket::AF_INET6 => "::1", } begin s = Socket.new(af, Socket::SOCK_STREAM, 0) begin tries = 5 begin sa = Socket.pack_sockaddr_in(1024 + Random.rand(63076), hosts[af]) s.bind(sa) rescue Errno::EADDRINUSE tries -= 1 retry if tries > 0 raise end yield rescue Errno::EADDRNOTAVAIL ensure s.close end rescue Errno::ESOCKTNOSUPPORT end end end def af_test(host) commands = { :ping => lambda { |*_| "+pong" }, } redis_mock(commands, :host => host) do |redis| assert_nothing_raised do redis.ping end end end driver(:ruby) do af_family_supported(Socket::AF_INET) do def test_connect_ipv4 af_test("127.0.0.1") end end end driver(:ruby) do af_family_supported(Socket::AF_INET6) do def test_connect_ipv6 af_test("::1") end end end def test_can_be_duped_to_create_a_new_connection clients = r.info["connected_clients"].to_i r2 = r.dup r2.ping assert_equal clients + 1, r.info["connected_clients"].to_i end end redis-3.2.2/test/command_map_test.rb0000644000004100000410000000111012637240307017467 0ustar www-datawww-data# encoding: UTF-8 require File.expand_path("helper", File.dirname(__FILE__)) class TestCommandMap < Test::Unit::TestCase include Helper::Client def test_override_existing_commands r.set("counter", 1) assert_equal 2, r.incr("counter") r.client.command_map[:incr] = :decr assert_equal 1, r.incr("counter") end def test_override_non_existing_commands r.set("key", "value") assert_raise Redis::CommandError do r.idontexist("key") end r.client.command_map[:idontexist] = :get assert_equal "value", r.idontexist("key") end end redis-3.2.2/test/helper.rb0000644000004100000410000001062712637240307015451 0ustar www-datawww-data$:.unshift File.expand_path("../lib", File.dirname(__FILE__)) $:.unshift File.expand_path(File.dirname(__FILE__)) require "test/unit" require "logger" require "stringio" (class Random; def self.rand(*args) super end; end) unless defined?(Random) begin require "ruby-debug" rescue LoadError end $VERBOSE = true ENV["conn"] ||= "ruby" require "redis" require "redis/distributed" require "redis/connection/#{ENV["conn"]}" require "support/redis_mock" require "support/connection/#{ENV["conn"]}" PORT = 6381 OPTIONS = {:port => PORT, :db => 15, :timeout => Float(ENV["TIMEOUT"] || 0.1)} NODES = ["redis://127.0.0.1:#{PORT}/15"] def init(redis) begin redis.select 14 redis.flushdb redis.select 15 redis.flushdb redis rescue Redis::CannotConnectError puts <<-EOS Cannot connect to Redis. Make sure Redis is running on localhost, port #{PORT}. This testing suite connects to the database 15. Try this once: $ rake clean Then run the build again: $ rake EOS exit 1 end end def driver(*drivers, &blk) if drivers.map(&:to_s).include?(ENV["conn"]) class_eval(&blk) end end module Helper def run(runner) if respond_to?(:around) around { super(runner) } else super end end def silent verbose, $VERBOSE = $VERBOSE, false begin yield ensure $VERBOSE = verbose end end def with_external_encoding(encoding) original_encoding = Encoding.default_external begin silent { Encoding.default_external = Encoding.find(encoding) } yield ensure silent { Encoding.default_external = original_encoding } end end def try_encoding(encoding, &block) if defined?(Encoding) with_external_encoding(encoding, &block) else yield end end class Version include Comparable attr :parts def initialize(v) case v when Version @parts = v.parts else @parts = v.to_s.split(".") end end def <=>(other) other = Version.new(other) length = [self.parts.length, other.parts.length].max length.times do |i| a, b = self.parts[i], other.parts[i] return -1 if a.nil? return +1 if b.nil? return a.to_i <=> b.to_i if a != b end 0 end end module Generic include Helper attr_reader :log attr_reader :redis alias :r :redis def setup @log = StringIO.new @redis = init _new_client # Run GC to make sure orphaned connections are closed. GC.start end def teardown @redis.quit if @redis end def redis_mock(commands, options = {}, &blk) RedisMock.start(commands, options) do |port| yield _new_client(options.merge(:port => port)) end end def redis_mock_with_handler(handler, options = {}, &blk) RedisMock.start_with_handler(handler, options) do |port| yield _new_client(options.merge(:port => port)) end end def assert_in_range(range, value) assert range.include?(value), "expected #{value} to be in #{range.inspect}" end def target_version(target) if version < target skip("Requires Redis > #{target}") if respond_to?(:skip) else yield end end end module Client include Generic def version Version.new(redis.info["redis_version"]) end private def _format_options(options) OPTIONS.merge(:logger => ::Logger.new(@log)).merge(options) end def _new_client(options = {}) Redis.new(_format_options(options).merge(:driver => ENV["conn"])) end end module Distributed include Generic def version Version.new(redis.info.first["redis_version"]) end private def _format_options(options) { :timeout => OPTIONS[:timeout], :logger => ::Logger.new(@log), }.merge(options) end def _new_client(options = {}) Redis::Distributed.new(NODES, _format_options(options).merge(:driver => ENV["conn"])) end end # Basic support for `skip` in 1.8.x # Note: YOU MUST use `return skip(message)` in order to appropriately bail # from a running test. module Skipable Skipped = Class.new(RuntimeError) def skip(message = nil, bt = caller) return super if defined?(super) $stderr.puts("SKIPPED: #{self} #{message || 'no reason given'}") end end end redis-3.2.2/test/lint/0000755000004100000410000000000012637240307014605 5ustar www-datawww-dataredis-3.2.2/test/lint/value_types.rb0000644000004100000410000000464412637240307017502 0ustar www-datawww-datamodule Lint module ValueTypes def test_exists assert_equal false, r.exists("foo") r.set("foo", "s1") assert_equal true, r.exists("foo") end def test_type assert_equal "none", r.type("foo") r.set("foo", "s1") assert_equal "string", r.type("foo") end def test_keys r.set("f", "s1") r.set("fo", "s2") r.set("foo", "s3") assert_equal ["f","fo", "foo"], r.keys("f*").sort end def test_expire r.set("foo", "s1") assert r.expire("foo", 2) assert_in_range 0..2, r.ttl("foo") end def test_pexpire target_version "2.5.4" do r.set("foo", "s1") assert r.pexpire("foo", 2000) assert_in_range 0..2, r.ttl("foo") end end def test_expireat r.set("foo", "s1") assert r.expireat("foo", (Time.now + 2).to_i) assert_in_range 0..2, r.ttl("foo") end def test_pexpireat target_version "2.5.4" do r.set("foo", "s1") assert r.pexpireat("foo", (Time.now + 2).to_i * 1_000) assert_in_range 0..2, r.ttl("foo") end end def test_persist r.set("foo", "s1") r.expire("foo", 1) r.persist("foo") assert(-1 == r.ttl("foo")) end def test_ttl r.set("foo", "s1") r.expire("foo", 2) assert_in_range 0..2, r.ttl("foo") end def test_pttl target_version "2.5.4" do r.set("foo", "s1") r.expire("foo", 2) assert_in_range 1..2000, r.pttl("foo") end end def test_dump_and_restore target_version "2.5.7" do r.set("foo", "a") v = r.dump("foo") r.del("foo") assert r.restore("foo", 1000, v) assert_equal "a", r.get("foo") assert [0, 1].include? r.ttl("foo") r.rpush("bar", ["b", "c", "d"]) w = r.dump("bar") r.del("bar") assert r.restore("bar", 1000, w) assert_equal ["b", "c", "d"], r.lrange("bar", 0, -1) assert [0, 1].include? r.ttl("bar") end end def test_move r.select 14 r.flushdb r.set "bar", "s3" r.select 15 r.set "foo", "s1" r.set "bar", "s2" assert r.move("foo", 14) assert_equal nil, r.get("foo") assert !r.move("bar", 14) assert_equal "s2", r.get("bar") r.select 14 assert_equal "s1", r.get("foo") assert_equal "s3", r.get("bar") end end end redis-3.2.2/test/lint/hashes.rb0000644000004100000410000000663512637240307016417 0ustar www-datawww-datamodule Lint module Hashes def test_hset_and_hget r.hset("foo", "f1", "s1") assert_equal "s1", r.hget("foo", "f1") end def test_hsetnx r.hset("foo", "f1", "s1") r.hsetnx("foo", "f1", "s2") assert_equal "s1", r.hget("foo", "f1") r.del("foo") r.hsetnx("foo", "f1", "s2") assert_equal "s2", r.hget("foo", "f1") end def test_hdel r.hset("foo", "f1", "s1") assert_equal "s1", r.hget("foo", "f1") assert_equal 1, r.hdel("foo", "f1") assert_equal nil, r.hget("foo", "f1") end def test_variadic_hdel target_version "2.3.9" do r.hset("foo", "f1", "s1") r.hset("foo", "f2", "s2") assert_equal "s1", r.hget("foo", "f1") assert_equal "s2", r.hget("foo", "f2") assert_equal 2, r.hdel("foo", ["f1", "f2"]) assert_equal nil, r.hget("foo", "f1") assert_equal nil, r.hget("foo", "f2") end end def test_hexists assert_equal false, r.hexists("foo", "f1") r.hset("foo", "f1", "s1") assert r.hexists("foo", "f1") end def test_hlen assert_equal 0, r.hlen("foo") r.hset("foo", "f1", "s1") assert_equal 1, r.hlen("foo") r.hset("foo", "f2", "s2") assert_equal 2, r.hlen("foo") end def test_hkeys assert_equal [], r.hkeys("foo") r.hset("foo", "f1", "s1") r.hset("foo", "f2", "s2") assert_equal ["f1", "f2"], r.hkeys("foo") end def test_hvals assert_equal [], r.hvals("foo") r.hset("foo", "f1", "s1") r.hset("foo", "f2", "s2") assert_equal ["s1", "s2"], r.hvals("foo") end def test_hgetall assert({} == r.hgetall("foo")) r.hset("foo", "f1", "s1") r.hset("foo", "f2", "s2") assert({"f1" => "s1", "f2" => "s2"} == r.hgetall("foo")) end def test_hmset r.hmset("hash", "foo1", "bar1", "foo2", "bar2") assert_equal "bar1", r.hget("hash", "foo1") assert_equal "bar2", r.hget("hash", "foo2") end def test_hmset_with_invalid_arguments assert_raise(Redis::CommandError) do r.hmset("hash", "foo1", "bar1", "foo2", "bar2", "foo3") end end def test_mapped_hmset r.mapped_hmset("foo", :f1 => "s1", :f2 => "s2") assert_equal "s1", r.hget("foo", "f1") assert_equal "s2", r.hget("foo", "f2") end def test_hmget r.hset("foo", "f1", "s1") r.hset("foo", "f2", "s2") r.hset("foo", "f3", "s3") assert_equal ["s2", "s3"], r.hmget("foo", "f2", "f3") end def test_hmget_mapped r.hset("foo", "f1", "s1") r.hset("foo", "f2", "s2") r.hset("foo", "f3", "s3") assert({"f1" => "s1"} == r.mapped_hmget("foo", "f1")) assert({"f1" => "s1", "f2" => "s2"} == r.mapped_hmget("foo", "f1", "f2")) end def test_hincrby r.hincrby("foo", "f1", 1) assert_equal "1", r.hget("foo", "f1") r.hincrby("foo", "f1", 2) assert_equal "3", r.hget("foo", "f1") r.hincrby("foo", "f1", -1) assert_equal "2", r.hget("foo", "f1") end def test_hincrbyfloat target_version "2.5.4" do r.hincrbyfloat("foo", "f1", 1.23) assert_equal "1.23", r.hget("foo", "f1") r.hincrbyfloat("foo", "f1", 0.77) assert_equal "2", r.hget("foo", "f1") r.hincrbyfloat("foo", "f1", -0.1) assert_equal "1.9", r.hget("foo", "f1") end end end end redis-3.2.2/test/lint/sorted_sets.rb0000644000004100000410000002602312637240307017473 0ustar www-datawww-datamodule Lint module SortedSets Infinity = 1.0/0.0 def test_zadd assert_equal 0, r.zcard("foo") assert_equal true, r.zadd("foo", 1, "s1") assert_equal false, r.zadd("foo", 1, "s1") assert_equal 1, r.zcard("foo") r.del "foo" target_version "3.0.2" do # XX option assert_equal 0, r.zcard("foo") assert_equal false, r.zadd("foo", 1, "s1", :xx => true) r.zadd("foo", 1, "s1") assert_equal false, r.zadd("foo", 2, "s1", :xx => true) assert_equal 2, r.zscore("foo", "s1") r.del "foo" # NX option assert_equal 0, r.zcard("foo") assert_equal true, r.zadd("foo", 1, "s1", :nx => true) assert_equal false, r.zadd("foo", 2, "s1", :nx => true) assert_equal 1, r.zscore("foo", "s1") assert_equal 1, r.zcard("foo") r.del "foo" # CH option assert_equal 0, r.zcard("foo") assert_equal true, r.zadd("foo", 1, "s1", :ch => true) assert_equal false, r.zadd("foo", 1, "s1", :ch => true) assert_equal true, r.zadd("foo", 2, "s1", :ch => true) assert_equal 1, r.zcard("foo") r.del "foo" # INCR option assert_equal 1.0, r.zadd("foo", 1, "s1", :incr => true) assert_equal 11.0, r.zadd("foo", 10, "s1", :incr => true) assert_equal -Infinity, r.zadd("bar", "-inf", "s1", :incr => true) assert_equal +Infinity, r.zadd("bar", "+inf", "s2", :incr => true) r.del "foo", "bar" # Incompatible options combination assert_raise(Redis::CommandError) { r.zadd("foo", 1, "s1", :xx => true, :nx => true) } end end def test_variadic_zadd target_version "2.3.9" do # 2.4-rc6 # Non-nested array with pairs assert_equal 0, r.zcard("foo") assert_equal 2, r.zadd("foo", [1, "s1", 2, "s2"]) assert_equal 1, r.zadd("foo", [4, "s1", 5, "s2", 6, "s3"]) assert_equal 3, r.zcard("foo") r.del "foo" # Nested array with pairs assert_equal 0, r.zcard("foo") assert_equal 2, r.zadd("foo", [[1, "s1"], [2, "s2"]]) assert_equal 1, r.zadd("foo", [[4, "s1"], [5, "s2"], [6, "s3"]]) assert_equal 3, r.zcard("foo") r.del "foo" # Wrong number of arguments assert_raise(Redis::CommandError) { r.zadd("foo", ["bar"]) } assert_raise(Redis::CommandError) { r.zadd("foo", ["bar", "qux", "zap"]) } end target_version "3.0.2" do # XX option assert_equal 0, r.zcard("foo") assert_equal 0, r.zadd("foo", [1, "s1", 2, "s2"], :xx => true) r.zadd("foo", [1, "s1", 2, "s2"]) assert_equal 0, r.zadd("foo", [2, "s1", 3, "s2", 4, "s3"], :xx => true) assert_equal 2, r.zscore("foo", "s1") assert_equal 3, r.zscore("foo", "s2") assert_equal nil, r.zscore("foo", "s3") assert_equal 2, r.zcard("foo") r.del "foo" # NX option assert_equal 0, r.zcard("foo") assert_equal 2, r.zadd("foo", [1, "s1", 2, "s2"], :nx => true) assert_equal 1, r.zadd("foo", [2, "s1", 3, "s2", 4, "s3"], :nx => true) assert_equal 1, r.zscore("foo", "s1") assert_equal 2, r.zscore("foo", "s2") assert_equal 4, r.zscore("foo", "s3") assert_equal 3, r.zcard("foo") r.del "foo" # CH option assert_equal 0, r.zcard("foo") assert_equal 2, r.zadd("foo", [1, "s1", 2, "s2"], :ch => true) assert_equal 2, r.zadd("foo", [1, "s1", 3, "s2", 4, "s3"], :ch => true) assert_equal 3, r.zcard("foo") r.del "foo" # INCR option assert_equal 1.0, r.zadd("foo", [1, "s1"], :incr => true) assert_equal 11.0, r.zadd("foo", [10, "s1"], :incr => true) assert_equal -Infinity, r.zadd("bar", ["-inf", "s1"], :incr => true) assert_equal +Infinity, r.zadd("bar", ["+inf", "s2"], :incr => true) assert_raise(Redis::CommandError) { r.zadd("foo", [1, "s1", 2, "s2"], :incr => true) } r.del "foo", "bar" # Incompatible options combination assert_raise(Redis::CommandError) { r.zadd("foo", [1, "s1"], :xx => true, :nx => true) } end end def test_zrem r.zadd("foo", 1, "s1") r.zadd("foo", 2, "s2") assert_equal 2, r.zcard("foo") assert_equal true, r.zrem("foo", "s1") assert_equal false, r.zrem("foo", "s1") assert_equal 1, r.zcard("foo") end def test_variadic_zrem target_version "2.3.9" do # 2.4-rc6 r.zadd("foo", 1, "s1") r.zadd("foo", 2, "s2") r.zadd("foo", 3, "s3") assert_equal 3, r.zcard("foo") assert_equal 1, r.zrem("foo", ["s1", "aaa"]) assert_equal 0, r.zrem("foo", ["bbb", "ccc" "ddd"]) assert_equal 1, r.zrem("foo", ["eee", "s3"]) assert_equal 1, r.zcard("foo") end end def test_zincrby rv = r.zincrby "foo", 1, "s1" assert_equal 1.0, rv rv = r.zincrby "foo", 10, "s1" assert_equal 11.0, rv rv = r.zincrby "bar", "-inf", "s1" assert_equal(-Infinity, rv) rv = r.zincrby "bar", "+inf", "s2" assert_equal(+Infinity, rv) end def test_zrank r.zadd "foo", 1, "s1" r.zadd "foo", 2, "s2" r.zadd "foo", 3, "s3" assert_equal 2, r.zrank("foo", "s3") end def test_zrevrank r.zadd "foo", 1, "s1" r.zadd "foo", 2, "s2" r.zadd "foo", 3, "s3" assert_equal 0, r.zrevrank("foo", "s3") end def test_zrange r.zadd "foo", 1, "s1" r.zadd "foo", 2, "s2" r.zadd "foo", 3, "s3" assert_equal ["s1", "s2"], r.zrange("foo", 0, 1) assert_equal [["s1", 1.0], ["s2", 2.0]], r.zrange("foo", 0, 1, :with_scores => true) assert_equal [["s1", 1.0], ["s2", 2.0]], r.zrange("foo", 0, 1, :withscores => true) r.zadd "bar", "-inf", "s1" r.zadd "bar", "+inf", "s2" assert_equal [["s1", -Infinity], ["s2", +Infinity]], r.zrange("bar", 0, 1, :with_scores => true) assert_equal [["s1", -Infinity], ["s2", +Infinity]], r.zrange("bar", 0, 1, :withscores => true) end def test_zrevrange r.zadd "foo", 1, "s1" r.zadd "foo", 2, "s2" r.zadd "foo", 3, "s3" assert_equal ["s3", "s2"], r.zrevrange("foo", 0, 1) assert_equal [["s3", 3.0], ["s2", 2.0]], r.zrevrange("foo", 0, 1, :with_scores => true) assert_equal [["s3", 3.0], ["s2", 2.0]], r.zrevrange("foo", 0, 1, :withscores => true) r.zadd "bar", "-inf", "s1" r.zadd "bar", "+inf", "s2" assert_equal [["s2", +Infinity], ["s1", -Infinity]], r.zrevrange("bar", 0, 1, :with_scores => true) assert_equal [["s2", +Infinity], ["s1", -Infinity]], r.zrevrange("bar", 0, 1, :withscores => true) end def test_zrangebyscore r.zadd "foo", 1, "s1" r.zadd "foo", 2, "s2" r.zadd "foo", 3, "s3" assert_equal ["s2", "s3"], r.zrangebyscore("foo", 2, 3) end def test_zrevrangebyscore r.zadd "foo", 1, "s1" r.zadd "foo", 2, "s2" r.zadd "foo", 3, "s3" assert_equal ["s3", "s2"], r.zrevrangebyscore("foo", 3, 2) end def test_zrangebyscore_with_limit r.zadd "foo", 1, "s1" r.zadd "foo", 2, "s2" r.zadd "foo", 3, "s3" r.zadd "foo", 4, "s4" assert_equal ["s2"], r.zrangebyscore("foo", 2, 4, :limit => [0, 1]) assert_equal ["s3"], r.zrangebyscore("foo", 2, 4, :limit => [1, 1]) assert_equal ["s3", "s4"], r.zrangebyscore("foo", 2, 4, :limit => [1, 2]) end def test_zrevrangebyscore_with_limit r.zadd "foo", 1, "s1" r.zadd "foo", 2, "s2" r.zadd "foo", 3, "s3" r.zadd "foo", 4, "s4" assert_equal ["s4"], r.zrevrangebyscore("foo", 4, 2, :limit => [0, 1]) assert_equal ["s3"], r.zrevrangebyscore("foo", 4, 2, :limit => [1, 1]) assert_equal ["s3", "s2"], r.zrevrangebyscore("foo", 4, 2, :limit => [1, 2]) end def test_zrangebyscore_with_withscores r.zadd "foo", 1, "s1" r.zadd "foo", 2, "s2" r.zadd "foo", 3, "s3" r.zadd "foo", 4, "s4" assert_equal [["s2", 2.0]], r.zrangebyscore("foo", 2, 4, :limit => [0, 1], :with_scores => true) assert_equal [["s3", 3.0]], r.zrangebyscore("foo", 2, 4, :limit => [1, 1], :with_scores => true) assert_equal [["s2", 2.0]], r.zrangebyscore("foo", 2, 4, :limit => [0, 1], :withscores => true) assert_equal [["s3", 3.0]], r.zrangebyscore("foo", 2, 4, :limit => [1, 1], :withscores => true) r.zadd "bar", "-inf", "s1" r.zadd "bar", "+inf", "s2" assert_equal [["s1", -Infinity]], r.zrangebyscore("bar", -Infinity, +Infinity, :limit => [0, 1], :with_scores => true) assert_equal [["s2", +Infinity]], r.zrangebyscore("bar", -Infinity, +Infinity, :limit => [1, 1], :with_scores => true) assert_equal [["s1", -Infinity]], r.zrangebyscore("bar", -Infinity, +Infinity, :limit => [0, 1], :withscores => true) assert_equal [["s2", +Infinity]], r.zrangebyscore("bar", -Infinity, +Infinity, :limit => [1, 1], :withscores => true) end def test_zrevrangebyscore_with_withscores r.zadd "foo", 1, "s1" r.zadd "foo", 2, "s2" r.zadd "foo", 3, "s3" r.zadd "foo", 4, "s4" assert_equal [["s4", 4.0]], r.zrevrangebyscore("foo", 4, 2, :limit => [0, 1], :with_scores => true) assert_equal [["s3", 3.0]], r.zrevrangebyscore("foo", 4, 2, :limit => [1, 1], :with_scores => true) assert_equal [["s4", 4.0]], r.zrevrangebyscore("foo", 4, 2, :limit => [0, 1], :withscores => true) assert_equal [["s3", 3.0]], r.zrevrangebyscore("foo", 4, 2, :limit => [1, 1], :withscores => true) r.zadd "bar", "-inf", "s1" r.zadd "bar", "+inf", "s2" assert_equal [["s2", +Infinity]], r.zrevrangebyscore("bar", +Infinity, -Infinity, :limit => [0, 1], :with_scores => true) assert_equal [["s1", -Infinity]], r.zrevrangebyscore("bar", +Infinity, -Infinity, :limit => [1, 1], :with_scores => true) assert_equal [["s2", +Infinity]], r.zrevrangebyscore("bar", +Infinity, -Infinity, :limit => [0, 1], :withscores => true) assert_equal [["s1", -Infinity]], r.zrevrangebyscore("bar", +Infinity, -Infinity, :limit => [1, 1], :withscores => true) end def test_zcard assert_equal 0, r.zcard("foo") r.zadd "foo", 1, "s1" assert_equal 1, r.zcard("foo") end def test_zscore r.zadd "foo", 1, "s1" assert_equal 1.0, r.zscore("foo", "s1") assert_equal nil, r.zscore("foo", "s2") assert_equal nil, r.zscore("bar", "s1") r.zadd "bar", "-inf", "s1" r.zadd "bar", "+inf", "s2" assert_equal(-Infinity, r.zscore("bar", "s1")) assert_equal(+Infinity, r.zscore("bar", "s2")) end def test_zremrangebyrank r.zadd "foo", 10, "s1" r.zadd "foo", 20, "s2" r.zadd "foo", 30, "s3" r.zadd "foo", 40, "s4" assert_equal 3, r.zremrangebyrank("foo", 1, 3) assert_equal ["s1"], r.zrange("foo", 0, -1) end def test_zremrangebyscore r.zadd "foo", 1, "s1" r.zadd "foo", 2, "s2" r.zadd "foo", 3, "s3" r.zadd "foo", 4, "s4" assert_equal 3, r.zremrangebyscore("foo", 2, 4) assert_equal ["s1"], r.zrange("foo", 0, -1) end end end redis-3.2.2/test/lint/lists.rb0000644000004100000410000000606012637240307016272 0ustar www-datawww-datamodule Lint module Lists def test_lpush r.lpush "foo", "s1" r.lpush "foo", "s2" assert_equal 2, r.llen("foo") assert_equal "s2", r.lpop("foo") end def test_variadic_lpush target_version "2.3.9" do # 2.4-rc6 assert_equal 3, r.lpush("foo", ["s1", "s2", "s3"]) assert_equal 3, r.llen("foo") assert_equal "s3", r.lpop("foo") end end def test_lpushx r.lpushx "foo", "s1" r.lpush "foo", "s2" r.lpushx "foo", "s3" assert_equal 2, r.llen("foo") assert_equal ["s3", "s2"], r.lrange("foo", 0, -1) end def test_rpush r.rpush "foo", "s1" r.rpush "foo", "s2" assert_equal 2, r.llen("foo") assert_equal "s2", r.rpop("foo") end def test_variadic_rpush target_version "2.3.9" do # 2.4-rc6 assert_equal 3, r.rpush("foo", ["s1", "s2", "s3"]) assert_equal 3, r.llen("foo") assert_equal "s3", r.rpop("foo") end end def test_rpushx r.rpushx "foo", "s1" r.rpush "foo", "s2" r.rpushx "foo", "s3" assert_equal 2, r.llen("foo") assert_equal ["s2", "s3"], r.lrange("foo", 0, -1) end def test_llen r.rpush "foo", "s1" r.rpush "foo", "s2" assert_equal 2, r.llen("foo") end def test_lrange r.rpush "foo", "s1" r.rpush "foo", "s2" r.rpush "foo", "s3" assert_equal ["s2", "s3"], r.lrange("foo", 1, -1) assert_equal ["s1", "s2"], r.lrange("foo", 0, 1) assert_equal [], r.lrange("bar", 0, -1) end def test_ltrim r.rpush "foo", "s1" r.rpush "foo", "s2" r.rpush "foo", "s3" r.ltrim "foo", 0, 1 assert_equal 2, r.llen("foo") assert_equal ["s1", "s2"], r.lrange("foo", 0, -1) end def test_lindex r.rpush "foo", "s1" r.rpush "foo", "s2" assert_equal "s1", r.lindex("foo", 0) assert_equal "s2", r.lindex("foo", 1) end def test_lset r.rpush "foo", "s1" r.rpush "foo", "s2" assert_equal "s2", r.lindex("foo", 1) assert r.lset("foo", 1, "s3") assert_equal "s3", r.lindex("foo", 1) assert_raise Redis::CommandError do r.lset("foo", 4, "s3") end end def test_lrem r.rpush "foo", "s1" r.rpush "foo", "s2" assert_equal 1, r.lrem("foo", 1, "s1") assert_equal ["s2"], r.lrange("foo", 0, -1) end def test_lpop r.rpush "foo", "s1" r.rpush "foo", "s2" assert_equal 2, r.llen("foo") assert_equal "s1", r.lpop("foo") assert_equal 1, r.llen("foo") end def test_rpop r.rpush "foo", "s1" r.rpush "foo", "s2" assert_equal 2, r.llen("foo") assert_equal "s2", r.rpop("foo") assert_equal 1, r.llen("foo") end def test_linsert r.rpush "foo", "s1" r.rpush "foo", "s3" r.linsert "foo", :before, "s3", "s2" assert_equal ["s1", "s2", "s3"], r.lrange("foo", 0, -1) assert_raise(Redis::CommandError) do r.linsert "foo", :anywhere, "s3", "s2" end end end end redis-3.2.2/test/lint/strings.rb0000644000004100000410000001316112637240307016625 0ustar www-datawww-datamodule Lint module Strings def test_set_and_get r.set("foo", "s1") assert_equal "s1", r.get("foo") end def test_set_and_get_with_brackets r["foo"] = "s1" assert_equal "s1", r["foo"] end def test_set_and_get_with_brackets_and_symbol r[:foo] = "s1" assert_equal "s1", r[:foo] end def test_set_and_get_with_newline_characters r.set("foo", "1\n") assert_equal "1\n", r.get("foo") end def test_set_and_get_with_non_string_value value = ["a", "b"] r.set("foo", value) assert_equal value.to_s, r.get("foo") end def test_set_and_get_with_ascii_characters if defined?(Encoding) with_external_encoding("ASCII-8BIT") do (0..255).each do |i| str = "#{i.chr}---#{i.chr}" r.set("foo", str) assert_equal str, r.get("foo") end end end end def test_set_with_ex target_version "2.6.12" do r.set("foo", "bar", :ex => 2) assert_in_range 0..2, r.ttl("foo") end end def test_set_with_px target_version "2.6.12" do r.set("foo", "bar", :px => 2000) assert_in_range 0..2, r.ttl("foo") end end def test_set_with_nx target_version "2.6.12" do r.set("foo", "qux", :nx => true) assert !r.set("foo", "bar", :nx => true) assert_equal "qux", r.get("foo") r.del("foo") assert r.set("foo", "bar", :nx => true) assert_equal "bar", r.get("foo") end end def test_set_with_xx target_version "2.6.12" do r.set("foo", "qux") assert r.set("foo", "bar", :xx => true) assert_equal "bar", r.get("foo") r.del("foo") assert !r.set("foo", "bar", :xx => true) end end def test_setex assert r.setex("foo", 1, "bar") assert_equal "bar", r.get("foo") assert [0, 1].include? r.ttl("foo") end def test_setex_with_non_string_value value = ["b", "a", "r"] assert r.setex("foo", 1, value) assert_equal value.to_s, r.get("foo") assert [0, 1].include? r.ttl("foo") end def test_psetex target_version "2.5.4" do assert r.psetex("foo", 1000, "bar") assert_equal "bar", r.get("foo") assert [0, 1].include? r.ttl("foo") end end def test_psetex_with_non_string_value target_version "2.5.4" do value = ["b", "a", "r"] assert r.psetex("foo", 1000, value) assert_equal value.to_s, r.get("foo") assert [0, 1].include? r.ttl("foo") end end def test_getset r.set("foo", "bar") assert_equal "bar", r.getset("foo", "baz") assert_equal "baz", r.get("foo") end def test_getset_with_non_string_value r.set("foo", "zap") value = ["b", "a", "r"] assert_equal "zap", r.getset("foo", value) assert_equal value.to_s, r.get("foo") end def test_setnx r.set("foo", "qux") assert !r.setnx("foo", "bar") assert_equal "qux", r.get("foo") r.del("foo") assert r.setnx("foo", "bar") assert_equal "bar", r.get("foo") end def test_setnx_with_non_string_value value = ["b", "a", "r"] r.set("foo", "qux") assert !r.setnx("foo", value) assert_equal "qux", r.get("foo") r.del("foo") assert r.setnx("foo", value) assert_equal value.to_s, r.get("foo") end def test_incr assert_equal 1, r.incr("foo") assert_equal 2, r.incr("foo") assert_equal 3, r.incr("foo") end def test_incrby assert_equal 1, r.incrby("foo", 1) assert_equal 3, r.incrby("foo", 2) assert_equal 6, r.incrby("foo", 3) end def test_incrbyfloat target_version "2.5.4" do assert_equal 1.23, r.incrbyfloat("foo", 1.23) assert_equal 2 , r.incrbyfloat("foo", 0.77) assert_equal 1.9 , r.incrbyfloat("foo", -0.1) end end def test_decr r.set("foo", 3) assert_equal 2, r.decr("foo") assert_equal 1, r.decr("foo") assert_equal 0, r.decr("foo") end def test_decrby r.set("foo", 6) assert_equal 3, r.decrby("foo", 3) assert_equal 1, r.decrby("foo", 2) assert_equal 0, r.decrby("foo", 1) end def test_append r.set "foo", "s" r.append "foo", "1" assert_equal "s1", r.get("foo") end def test_getbit r.set("foo", "a") assert_equal 1, r.getbit("foo", 1) assert_equal 1, r.getbit("foo", 2) assert_equal 0, r.getbit("foo", 3) assert_equal 0, r.getbit("foo", 4) assert_equal 0, r.getbit("foo", 5) assert_equal 0, r.getbit("foo", 6) assert_equal 1, r.getbit("foo", 7) end def test_setbit r.set("foo", "a") r.setbit("foo", 6, 1) assert_equal "c", r.get("foo") end def test_bitcount target_version "2.5.10" do r.set("foo", "abcde") assert_equal 10, r.bitcount("foo", 1, 3) assert_equal 17, r.bitcount("foo", 0, -1) end end def test_getrange r.set("foo", "abcde") assert_equal "bcd", r.getrange("foo", 1, 3) assert_equal "abcde", r.getrange("foo", 0, -1) end def test_setrange r.set("foo", "abcde") r.setrange("foo", 1, "bar") assert_equal "abare", r.get("foo") end def test_setrange_with_non_string_value r.set("foo", "abcde") value = ["b", "a", "r"] r.setrange("foo", 2, value) assert_equal "ab#{value.to_s}", r.get("foo") end def test_strlen r.set "foo", "lorem" assert_equal 5, r.strlen("foo") end end end redis-3.2.2/test/lint/sets.rb0000644000004100000410000000542312637240307016114 0ustar www-datawww-datamodule Lint module Sets def test_sadd assert_equal true, r.sadd("foo", "s1") assert_equal true, r.sadd("foo", "s2") assert_equal false, r.sadd("foo", "s1") assert_equal ["s1", "s2"], r.smembers("foo").sort end def test_variadic_sadd target_version "2.3.9" do # 2.4-rc6 assert_equal 2, r.sadd("foo", ["s1", "s2"]) assert_equal 1, r.sadd("foo", ["s1", "s2", "s3"]) assert_equal ["s1", "s2", "s3"], r.smembers("foo").sort end end def test_srem r.sadd("foo", "s1") r.sadd("foo", "s2") assert_equal true, r.srem("foo", "s1") assert_equal false, r.srem("foo", "s3") assert_equal ["s2"], r.smembers("foo") end def test_variadic_srem target_version "2.3.9" do # 2.4-rc6 r.sadd("foo", "s1") r.sadd("foo", "s2") r.sadd("foo", "s3") assert_equal 1, r.srem("foo", ["s1", "aaa"]) assert_equal 0, r.srem("foo", ["bbb", "ccc" "ddd"]) assert_equal 1, r.srem("foo", ["eee", "s3"]) assert_equal ["s2"], r.smembers("foo") end end def test_spop r.sadd "foo", "s1" r.sadd "foo", "s2" assert ["s1", "s2"].include?(r.spop("foo")) assert ["s1", "s2"].include?(r.spop("foo")) assert_equal nil, r.spop("foo") end def test_scard assert_equal 0, r.scard("foo") r.sadd "foo", "s1" assert_equal 1, r.scard("foo") r.sadd "foo", "s2" assert_equal 2, r.scard("foo") end def test_sismember assert_equal false, r.sismember("foo", "s1") r.sadd "foo", "s1" assert_equal true, r.sismember("foo", "s1") assert_equal false, r.sismember("foo", "s2") end def test_smembers assert_equal [], r.smembers("foo") r.sadd "foo", "s1" r.sadd "foo", "s2" assert_equal ["s1", "s2"], r.smembers("foo").sort end def test_srandmember r.sadd "foo", "s1" r.sadd "foo", "s2" 4.times do assert ["s1", "s2"].include?(r.srandmember("foo")) end assert_equal 2, r.scard("foo") end def test_srandmember_with_positive_count r.sadd "foo", "s1" r.sadd "foo", "s2" r.sadd "foo", "s3" r.sadd "foo", "s4" 4.times do assert !(["s1", "s2", "s3", "s4"] & r.srandmember("foo", 3)).empty? assert_equal 3, r.srandmember("foo", 3).size end assert_equal 4, r.scard("foo") end def test_srandmember_with_negative_count r.sadd "foo", "s1" r.sadd "foo", "s2" r.sadd "foo", "s3" r.sadd "foo", "s4" 4.times do assert !(["s1", "s2", "s3", "s4"] & r.srandmember("foo", -6)).empty? assert_equal 6, r.srandmember("foo", -6).size end assert_equal 4, r.scard("foo") end end end redis-3.2.2/test/lint/hyper_log_log.rb0000644000004100000410000000264512637240307017772 0ustar www-datawww-datamodule Lint module HyperLogLog def test_pfadd target_version "2.8.9" do assert_equal true, r.pfadd("foo", "s1") assert_equal true, r.pfadd("foo", "s2") assert_equal false, r.pfadd("foo", "s1") assert_equal 2, r.pfcount("foo") end end def test_variadic_pfadd target_version "2.8.9" do assert_equal true, r.pfadd("foo", ["s1", "s2"]) assert_equal true, r.pfadd("foo", ["s1", "s2", "s3"]) assert_equal 3, r.pfcount("foo") end end def test_pfcount target_version "2.8.9" do assert_equal 0, r.pfcount("foo") assert_equal true, r.pfadd("foo", "s1") assert_equal 1, r.pfcount("foo") end end def test_variadic_pfcount target_version "2.8.9" do assert_equal 0, r.pfcount(["{1}foo", "{1}bar"]) assert_equal true, r.pfadd("{1}foo", "s1") assert_equal true, r.pfadd("{1}bar", "s1") assert_equal true, r.pfadd("{1}bar", "s2") assert_equal 2, r.pfcount("{1}foo", "{1}bar") end end def test_variadic_pfcount_expanded target_version "2.8.9" do assert_equal 0, r.pfcount("{1}foo", "{1}bar") assert_equal true, r.pfadd("{1}foo", "s1") assert_equal true, r.pfadd("{1}bar", "s1") assert_equal true, r.pfadd("{1}bar", "s2") assert_equal 2, r.pfcount("{1}foo", "{1}bar") end end end end redis-3.2.2/test/lint/blocking_commands.rb0000644000004100000410000001066112637240307020607 0ustar www-datawww-datamodule Lint module BlockingCommands def setup super r.rpush("{zap}foo", "s1") r.rpush("{zap}foo", "s2") r.rpush("{zap}bar", "s1") r.rpush("{zap}bar", "s2") end def to_protocol(obj) case obj when String "$#{obj.length}\r\n#{obj}\r\n" when Array "*#{obj.length}\r\n" + obj.map { |e| to_protocol(e) }.join else fail end end def mock(options = {}, &blk) commands = { :blpop => lambda do |*args| sleep options[:delay] if options.has_key?(:delay) to_protocol([args.first, args.last]) end, :brpop => lambda do |*args| sleep options[:delay] if options.has_key?(:delay) to_protocol([args.first, args.last]) end, :brpoplpush => lambda do |*args| sleep options[:delay] if options.has_key?(:delay) to_protocol(args.last) end, } redis_mock(commands, &blk) end def test_blpop assert_equal ["{zap}foo", "s1"], r.blpop("{zap}foo") assert_equal ["{zap}foo", "s2"], r.blpop(["{zap}foo"]) assert_equal ["{zap}bar", "s1"], r.blpop(["{zap}bar", "{zap}foo"]) assert_equal ["{zap}bar", "s2"], r.blpop(["{zap}foo", "{zap}bar"]) end def test_blpop_timeout mock do |r| assert_equal ["{zap}foo", "0"], r.blpop("{zap}foo") assert_equal ["{zap}foo", "1"], r.blpop("{zap}foo", :timeout => 1) end end def test_blpop_with_old_prototype assert_equal ["{zap}foo", "s1"], r.blpop("{zap}foo", 0) assert_equal ["{zap}foo", "s2"], r.blpop("{zap}foo", 0) assert_equal ["{zap}bar", "s1"], r.blpop("{zap}bar", "{zap}foo", 0) assert_equal ["{zap}bar", "s2"], r.blpop("{zap}foo", "{zap}bar", 0) end def test_blpop_timeout_with_old_prototype mock do |r| assert_equal ["{zap}foo", "0"], r.blpop("{zap}foo", 0) assert_equal ["{zap}foo", "1"], r.blpop("{zap}foo", 1) end end def test_brpop assert_equal ["{zap}foo", "s2"], r.brpop("{zap}foo") assert_equal ["{zap}foo", "s1"], r.brpop(["{zap}foo"]) assert_equal ["{zap}bar", "s2"], r.brpop(["{zap}bar", "{zap}foo"]) assert_equal ["{zap}bar", "s1"], r.brpop(["{zap}foo", "{zap}bar"]) end def test_brpop_timeout mock do |r| assert_equal ["{zap}foo", "0"], r.brpop("{zap}foo") assert_equal ["{zap}foo", "1"], r.brpop("{zap}foo", :timeout => 1) end end def test_brpop_with_old_prototype assert_equal ["{zap}foo", "s2"], r.brpop("{zap}foo", 0) assert_equal ["{zap}foo", "s1"], r.brpop("{zap}foo", 0) assert_equal ["{zap}bar", "s2"], r.brpop("{zap}bar", "{zap}foo", 0) assert_equal ["{zap}bar", "s1"], r.brpop("{zap}foo", "{zap}bar", 0) end def test_brpop_timeout_with_old_prototype mock do |r| assert_equal ["{zap}foo", "0"], r.brpop("{zap}foo", 0) assert_equal ["{zap}foo", "1"], r.brpop("{zap}foo", 1) end end def test_brpoplpush assert_equal "s2", r.brpoplpush("{zap}foo", "{zap}qux") assert_equal ["s2"], r.lrange("{zap}qux", 0, -1) end def test_brpoplpush_timeout mock do |r| assert_equal "0", r.brpoplpush("{zap}foo", "{zap}bar") assert_equal "1", r.brpoplpush("{zap}foo", "{zap}bar", :timeout => 1) end end def test_brpoplpush_with_old_prototype assert_equal "s2", r.brpoplpush("{zap}foo", "{zap}qux", 0) assert_equal ["s2"], r.lrange("{zap}qux", 0, -1) end def test_brpoplpush_timeout_with_old_prototype mock do |r| assert_equal "0", r.brpoplpush("{zap}foo", "{zap}bar", 0) assert_equal "1", r.brpoplpush("{zap}foo", "{zap}bar", 1) end end driver(:ruby, :hiredis) do def test_blpop_socket_timeout mock(:delay => 1 + OPTIONS[:timeout] * 2) do |r| assert_raises(Redis::TimeoutError) do r.blpop("{zap}foo", :timeout => 1) end end end def test_brpop_socket_timeout mock(:delay => 1 + OPTIONS[:timeout] * 2) do |r| assert_raises(Redis::TimeoutError) do r.brpop("{zap}foo", :timeout => 1) end end end def test_brpoplpush_socket_timeout mock(:delay => 1 + OPTIONS[:timeout] * 2) do |r| assert_raises(Redis::TimeoutError) do r.brpoplpush("{zap}foo", "{zap}bar", :timeout => 1) end end end end end end redis-3.2.2/test/commands_on_lists_test.rb0000644000004100000410000000073612637240307020744 0ustar www-datawww-data# encoding: UTF-8 require File.expand_path("helper", File.dirname(__FILE__)) require "lint/lists" class TestCommandsOnLists < Test::Unit::TestCase include Helper::Client include Lint::Lists def test_rpoplpush r.rpush "foo", "s1" r.rpush "foo", "s2" assert_equal "s2", r.rpoplpush("foo", "bar") assert_equal ["s2"], r.lrange("bar", 0, -1) assert_equal "s1", r.rpoplpush("foo", "bar") assert_equal ["s1", "s2"], r.lrange("bar", 0, -1) end end redis-3.2.2/test/distributed_commands_on_sets_test.rb0000644000004100000410000000325112637240307023161 0ustar www-datawww-data# encoding: UTF-8 require File.expand_path("helper", File.dirname(__FILE__)) require "lint/sets" class TestDistributedCommandsOnSets < Test::Unit::TestCase include Helper::Distributed include Lint::Sets def test_smove assert_raise Redis::Distributed::CannotDistribute do r.sadd "foo", "s1" r.sadd "bar", "s2" r.smove("foo", "bar", "s1") end end def test_sinter assert_raise Redis::Distributed::CannotDistribute do r.sadd "foo", "s1" r.sadd "foo", "s2" r.sadd "bar", "s2" r.sinter("foo", "bar") end end def test_sinterstore assert_raise Redis::Distributed::CannotDistribute do r.sadd "foo", "s1" r.sadd "foo", "s2" r.sadd "bar", "s2" r.sinterstore("baz", "foo", "bar") end end def test_sunion assert_raise Redis::Distributed::CannotDistribute do r.sadd "foo", "s1" r.sadd "foo", "s2" r.sadd "bar", "s2" r.sadd "bar", "s3" r.sunion("foo", "bar") end end def test_sunionstore assert_raise Redis::Distributed::CannotDistribute do r.sadd "foo", "s1" r.sadd "foo", "s2" r.sadd "bar", "s2" r.sadd "bar", "s3" r.sunionstore("baz", "foo", "bar") end end def test_sdiff assert_raise Redis::Distributed::CannotDistribute do r.sadd "foo", "s1" r.sadd "foo", "s2" r.sadd "bar", "s2" r.sadd "bar", "s3" r.sdiff("foo", "bar") end end def test_sdiffstore assert_raise Redis::Distributed::CannotDistribute do r.sadd "foo", "s1" r.sadd "foo", "s2" r.sadd "bar", "s2" r.sadd "bar", "s3" r.sdiffstore("baz", "foo", "bar") end end end redis-3.2.2/test/distributed_blocking_commands_test.rb0000644000004100000410000000207712637240307023304 0ustar www-datawww-data# encoding: UTF-8 require File.expand_path("helper", File.dirname(__FILE__)) require "lint/blocking_commands" class TestDistributedBlockingCommands < Test::Unit::TestCase include Helper::Distributed include Lint::BlockingCommands def test_blpop_raises assert_raises(Redis::Distributed::CannotDistribute) do r.blpop(["foo", "bar"]) end end def test_blpop_raises_with_old_prototype assert_raises(Redis::Distributed::CannotDistribute) do r.blpop("foo", "bar", 0) end end def test_brpop_raises assert_raises(Redis::Distributed::CannotDistribute) do r.brpop(["foo", "bar"]) end end def test_brpop_raises_with_old_prototype assert_raises(Redis::Distributed::CannotDistribute) do r.brpop("foo", "bar", 0) end end def test_brpoplpush_raises assert_raises(Redis::Distributed::CannotDistribute) do r.brpoplpush("foo", "bar") end end def test_brpoplpush_raises_with_old_prototype assert_raises(Redis::Distributed::CannotDistribute) do r.brpoplpush("foo", "bar", 0) end end end redis-3.2.2/test/scripting_test.rb0000644000004100000410000000457312637240307017236 0ustar www-datawww-data# encoding: UTF-8 require File.expand_path("helper", File.dirname(__FILE__)) class TestScripting < Test::Unit::TestCase include Helper::Client def to_sha(script) r.script(:load, script) end def test_script_exists target_version "2.5.9" do # 2.6-rc1 a = to_sha("return 1") b = a.succ assert_equal true, r.script(:exists, a) assert_equal false, r.script(:exists, b) assert_equal [true], r.script(:exists, [a]) assert_equal [false], r.script(:exists, [b]) assert_equal [true, false], r.script(:exists, [a, b]) end end def test_script_flush target_version "2.5.9" do # 2.6-rc1 sha = to_sha("return 1") assert r.script(:exists, sha) assert_equal "OK", r.script(:flush) assert !r.script(:exists, sha) end end def test_script_kill target_version "2.5.9" do # 2.6-rc1 redis_mock(:script => lambda { |arg| "+#{arg.upcase}" }) do |redis| assert_equal "KILL", redis.script(:kill) end end end def test_eval target_version "2.5.9" do # 2.6-rc1 assert_equal 0, r.eval("return #KEYS") assert_equal 0, r.eval("return #ARGV") assert_equal ["k1", "k2"], r.eval("return KEYS", ["k1", "k2"]) assert_equal ["a1", "a2"], r.eval("return ARGV", [], ["a1", "a2"]) end end def test_eval_with_options_hash target_version "2.5.9" do # 2.6-rc1 assert_equal 0, r.eval("return #KEYS", {}) assert_equal 0, r.eval("return #ARGV", {}) assert_equal ["k1", "k2"], r.eval("return KEYS", { :keys => ["k1", "k2"] }) assert_equal ["a1", "a2"], r.eval("return ARGV", { :argv => ["a1", "a2"] }) end end def test_evalsha target_version "2.5.9" do # 2.6-rc1 assert_equal 0, r.evalsha(to_sha("return #KEYS")) assert_equal 0, r.evalsha(to_sha("return #ARGV")) assert_equal ["k1", "k2"], r.evalsha(to_sha("return KEYS"), ["k1", "k2"]) assert_equal ["a1", "a2"], r.evalsha(to_sha("return ARGV"), [], ["a1", "a2"]) end end def test_evalsha_with_options_hash target_version "2.5.9" do # 2.6-rc1 assert_equal 0, r.evalsha(to_sha("return #KEYS"), {}) assert_equal 0, r.evalsha(to_sha("return #ARGV"), {}) assert_equal ["k1", "k2"], r.evalsha(to_sha("return KEYS"), { :keys => ["k1", "k2"] }) assert_equal ["a1", "a2"], r.evalsha(to_sha("return ARGV"), { :argv => ["a1", "a2"] }) end end end redis-3.2.2/test/url_param_test.rb0000644000004100000410000001032312637240307017204 0ustar www-datawww-data# encoding: UTF-8 require File.expand_path("helper", File.dirname(__FILE__)) class TestUrlParam < Test::Unit::TestCase include Helper::Client def test_url_defaults_to_______________ redis = Redis.new assert_equal "127.0.0.1", redis.client.host assert_equal 6379, redis.client.port assert_equal 0, redis.client.db assert_equal nil, redis.client.password end def test_allows_to_pass_in_a_url redis = Redis.new :url => "redis://:secr3t@foo.com:999/2" assert_equal "foo.com", redis.client.host assert_equal 999, redis.client.port assert_equal 2, redis.client.db assert_equal "secr3t", redis.client.password end def test_allows_to_pass_in_a_url_with_string_key redis = Redis.new "url" => "redis://:secr3t@foo.com:999/2" assert_equal "foo.com", redis.client.host assert_equal 999, redis.client.port assert_equal 2, redis.client.db assert_equal "secr3t", redis.client.password end def test_unescape_password_from_url redis = Redis.new :url => "redis://:secr3t%3A@foo.com:999/2" assert_equal "secr3t:", redis.client.password end def test_unescape_password_from_url_with_string_key redis = Redis.new "url" => "redis://:secr3t%3A@foo.com:999/2" assert_equal "secr3t:", redis.client.password end def test_does_not_unescape_password_when_explicitly_passed redis = Redis.new :url => "redis://:secr3t%3A@foo.com:999/2", :password => "secr3t%3A" assert_equal "secr3t%3A", redis.client.password end def test_does_not_unescape_password_when_explicitly_passed_with_string_key redis = Redis.new :url => "redis://:secr3t%3A@foo.com:999/2", "password" => "secr3t%3A" assert_equal "secr3t%3A", redis.client.password end def test_override_url_if_path_option_is_passed redis = Redis.new :url => "redis://:secr3t@foo.com/foo:999/2", :path => "/tmp/redis.sock" assert_equal "/tmp/redis.sock", redis.client.path assert_equal nil, redis.client.host assert_equal nil, redis.client.port end def test_override_url_if_path_option_is_passed_with_string_key redis = Redis.new :url => "redis://:secr3t@foo.com/foo:999/2", "path" => "/tmp/redis.sock" assert_equal "/tmp/redis.sock", redis.client.path assert_equal nil, redis.client.host assert_equal nil, redis.client.port end def test_overrides_url_if_another_connection_option_is_passed redis = Redis.new :url => "redis://:secr3t@foo.com:999/2", :port => 1000 assert_equal "foo.com", redis.client.host assert_equal 1000, redis.client.port assert_equal 2, redis.client.db assert_equal "secr3t", redis.client.password end def test_overrides_url_if_another_connection_option_is_passed_with_string_key redis = Redis.new :url => "redis://:secr3t@foo.com:999/2", "port" => 1000 assert_equal "foo.com", redis.client.host assert_equal 1000, redis.client.port assert_equal 2, redis.client.db assert_equal "secr3t", redis.client.password end def test_does_not_overrides_url_if_a_nil_option_is_passed redis = Redis.new :url => "redis://:secr3t@foo.com:999/2", :port => nil assert_equal "foo.com", redis.client.host assert_equal 999, redis.client.port assert_equal 2, redis.client.db assert_equal "secr3t", redis.client.password end def test_does_not_overrides_url_if_a_nil_option_is_passed_with_string_key redis = Redis.new :url => "redis://:secr3t@foo.com:999/2", "port" => nil assert_equal "foo.com", redis.client.host assert_equal 999, redis.client.port assert_equal 2, redis.client.db assert_equal "secr3t", redis.client.password end def test_does_not_modify_the_passed_options options = { :url => "redis://:secr3t@foo.com:999/2" } Redis.new(options) assert({ :url => "redis://:secr3t@foo.com:999/2" } == options) end def test_uses_redis_url_over_default_if_available ENV["REDIS_URL"] = "redis://:secr3t@foo.com:999/2" redis = Redis.new assert_equal "foo.com", redis.client.host assert_equal 999, redis.client.port assert_equal 2, redis.client.db assert_equal "secr3t", redis.client.password ENV.delete("REDIS_URL") end def test_defaults_to_localhost redis = Redis.new(:url => "redis:///") assert_equal "127.0.0.1", redis.client.host end end redis-3.2.2/test/db/0000755000004100000410000000000012637240307014224 5ustar www-datawww-dataredis-3.2.2/test/db/.gitkeep0000644000004100000410000000000012637240307015643 0ustar www-datawww-dataredis-3.2.2/test/distributed_commands_on_sorted_sets_test.rb0000644000004100000410000000057312637240307024545 0ustar www-datawww-data# encoding: UTF-8 require File.expand_path("helper", File.dirname(__FILE__)) require "lint/sorted_sets" class TestDistributedCommandsOnSortedSets < Test::Unit::TestCase include Helper::Distributed include Lint::SortedSets def test_zcount r.zadd "foo", 1, "s1" r.zadd "foo", 2, "s2" r.zadd "foo", 3, "s3" assert_equal 2, r.zcount("foo", 2, 3) end end redis-3.2.2/test/blocking_commands_test.rb0000644000004100000410000000171712637240307020702 0ustar www-datawww-data# encoding: UTF-8 require File.expand_path("helper", File.dirname(__FILE__)) require "lint/blocking_commands" class TestBlockingCommands < Test::Unit::TestCase include Helper::Client include Lint::BlockingCommands def assert_takes_longer_than_client_timeout timeout = OPTIONS[:timeout] delay = timeout * 2 mock(:delay => delay) do |r| t1 = Time.now yield(r) t2 = Time.now assert timeout == r.client.timeout assert delay <= (t2 - t1) end end def test_blpop_disable_client_timeout assert_takes_longer_than_client_timeout do |r| assert_equal ["foo", "0"], r.blpop("foo") end end def test_brpop_disable_client_timeout assert_takes_longer_than_client_timeout do |r| assert_equal ["foo", "0"], r.brpop("foo") end end def test_brpoplpush_disable_client_timeout assert_takes_longer_than_client_timeout do |r| assert_equal "0", r.brpoplpush("foo", "bar") end end end redis-3.2.2/test/distributed_remote_server_control_commands_test.rb0000644000004100000410000000272012637240307026130 0ustar www-datawww-data# encoding: UTF-8 require File.expand_path("helper", File.dirname(__FILE__)) class TestDistributedRemoteServerControlCommands < Test::Unit::TestCase include Helper::Distributed def test_info keys = [ "redis_version", "uptime_in_seconds", "uptime_in_days", "connected_clients", "used_memory", "total_connections_received", "total_commands_processed", ] infos = r.info infos.each do |info| keys.each do |k| msg = "expected #info to include #{k}" assert info.keys.include?(k), msg end end end def test_info_commandstats target_version "2.5.7" do r.nodes.each { |n| n.config(:resetstat) } r.ping # Executed on every node r.info(:commandstats).each do |info| assert_equal "1", info["ping"]["calls"] end end end def test_monitor begin r.monitor rescue Exception => ex ensure assert ex.kind_of?(NotImplementedError) end end def test_echo assert_equal ["foo bar baz\n"], r.echo("foo bar baz\n") end def test_time target_version "2.5.4" do # Test that the difference between the time that Ruby reports and the time # that Redis reports is minimal (prevents the test from being racy). r.time.each do |rv| redis_usec = rv[0] * 1_000_000 + rv[1] ruby_usec = Integer(Time.now.to_f * 1_000_000) assert 500_000 > (ruby_usec - redis_usec).abs end end end end redis-3.2.2/test/distributed_connection_handling_test.rb0000644000004100000410000000060512637240307023631 0ustar www-datawww-data# encoding: UTF-8 require File.expand_path("helper", File.dirname(__FILE__)) class TestDistributedConnectionHandling < Test::Unit::TestCase include Helper::Distributed def test_ping assert_equal ["PONG"], r.ping end def test_select r.set "foo", "bar" r.select 14 assert_equal nil, r.get("foo") r.select 15 assert_equal "bar", r.get("foo") end end redis-3.2.2/test/error_replies_test.rb0000644000004100000410000000274412637240307020106 0ustar www-datawww-data# encoding: UTF-8 require File.expand_path("helper", File.dirname(__FILE__)) class TestErrorReplies < Test::Unit::TestCase include Helper::Client # Every test shouldn't disconnect from the server. Also, when error replies are # in play, the protocol should never get into an invalid state where there are # pending replies in the connection. Calling INFO after every test ensures that # the protocol is still in a valid state. def with_reconnection_check before = r.info["total_connections_received"] yield(r) after = r.info["total_connections_received"] ensure assert_equal before, after end def test_error_reply_for_single_command with_reconnection_check do begin r.unknown_command rescue => ex ensure assert ex.message =~ /unknown command/i end end end def test_raise_first_error_reply_in_pipeline with_reconnection_check do begin r.pipelined do r.set("foo", "s1") r.incr("foo") # not an integer r.lpush("foo", "value") # wrong kind of value end rescue => ex ensure assert ex.message =~ /not an integer/i end end end def test_recover_from_raise_in__call_loop with_reconnection_check do begin r.client.call_loop([:invalid_monitor]) do assert false # Should never be executed end rescue => ex ensure assert ex.message =~ /unknown command/i end end end end redis-3.2.2/test/distributed_commands_on_hyper_log_log_test.rb0000644000004100000410000000133712637240307025037 0ustar www-datawww-data# encoding: UTF-8 require File.expand_path("helper", File.dirname(__FILE__)) require "lint/hyper_log_log" class TestDistributedCommandsOnHyperLogLog < Test::Unit::TestCase include Helper::Distributed include Lint::HyperLogLog def test_pfmerge target_version "2.8.9" do assert_raise Redis::Distributed::CannotDistribute do r.pfadd "foo", "s1" r.pfadd "bar", "s2" assert r.pfmerge("res", "foo", "bar") end end end def test_pfcount_multiple_keys_diff_nodes target_version "2.8.9" do assert_raise Redis::Distributed::CannotDistribute do r.pfadd "foo", "s1" r.pfadd "bar", "s2" assert r.pfcount("res", "foo", "bar") end end end end redis-3.2.2/test/distributed_internals_test.rb0000644000004100000410000000512412637240307021626 0ustar www-datawww-data# encoding: UTF-8 require File.expand_path("helper", File.dirname(__FILE__)) class TestDistributedInternals < Test::Unit::TestCase include Helper::Distributed def test_provides_a_meaningful_inspect nodes = ["redis://localhost:#{PORT}/15", *NODES] redis = Redis::Distributed.new nodes assert_equal "#", redis.inspect end def test_default_as_urls nodes = ["redis://localhost:#{PORT}/15", *NODES] redis = Redis::Distributed.new nodes assert_equal ["redis://localhost:#{PORT}/15", *NODES], redis.nodes.map { |node| node.client.id} end def test_default_as_config_hashes nodes = [OPTIONS.merge(:host => '127.0.0.1'), OPTIONS.merge(:host => 'somehost', :port => PORT.next)] redis = Redis::Distributed.new nodes assert_equal ["redis://127.0.0.1:#{PORT}/15","redis://somehost:#{PORT.next}/15"], redis.nodes.map { |node| node.client.id } end def test_as_mix_and_match nodes = ["redis://127.0.0.1:7389/15", OPTIONS.merge(:host => 'somehost'), OPTIONS.merge(:host => 'somehost', :port => PORT.next)] redis = Redis::Distributed.new nodes assert_equal ["redis://127.0.0.1:7389/15", "redis://somehost:#{PORT}/15", "redis://somehost:#{PORT.next}/15"], redis.nodes.map { |node| node.client.id } end def test_override_id nodes = [OPTIONS.merge(:host => '127.0.0.1', :id => "test"), OPTIONS.merge( :host => 'somehost', :port => PORT.next, :id => "test1")] redis = Redis::Distributed.new nodes assert_equal redis.nodes.first.client.id, "test" assert_equal redis.nodes.last.client.id, "test1" assert_equal "#", redis.inspect end def test_can_be_duped_to_create_a_new_connection redis = Redis::Distributed.new(NODES) clients = redis.info[0]["connected_clients"].to_i r2 = redis.dup r2.ping assert_equal clients + 1, redis.info[0]["connected_clients"].to_i end def test_keeps_options_after_dup r1 = Redis::Distributed.new(NODES, :tag => /^(\w+):/) assert_raise(Redis::Distributed::CannotDistribute) do r1.sinter("foo", "bar") end assert_equal [], r1.sinter("baz:foo", "baz:bar") r2 = r1.dup assert_raise(Redis::Distributed::CannotDistribute) do r2.sinter("foo", "bar") end assert_equal [], r2.sinter("baz:foo", "baz:bar") end def test_colliding_node_ids nodes = ["redis://localhost:#{PORT}/15", "redis://localhost:#{PORT}/15", *NODES] assert_raise(RuntimeError) do redis = Redis::Distributed.new nodes end end end redis-3.2.2/test/sentinel_command_test.rb0000644000004100000410000000415512637240307020547 0ustar www-datawww-data# encoding: UTF-8 require File.expand_path("helper", File.dirname(__FILE__)) class SentinalCommandsTest < Test::Unit::TestCase include Helper::Client def test_sentinel_command_master handler = lambda do |id| { :sentinel => lambda do |command, *args| ["name", "master1", "ip", "127.0.0.1"] end } end RedisMock.start(handler.call(:s1)) do |port| redis = Redis.new(:host => "127.0.0.1", :port => port) result = redis.sentinel('master', 'master1') assert_equal result, { "name" => "master1", "ip" => "127.0.0.1" } end end def test_sentinel_command_masters handler = lambda do |id| { :sentinel => lambda do |command, *args| [%w[name master1 ip 127.0.0.1 port 6381], %w[name master1 ip 127.0.0.1 port 6382]] end } end RedisMock.start(handler.call(:s1)) do |port| redis = Redis.new(:host => "127.0.0.1", :port => port) result = redis.sentinel('masters') assert_equal result[0], { "name" => "master1", "ip" => "127.0.0.1", "port" => "6381" } assert_equal result[1], { "name" => "master1", "ip" => "127.0.0.1", "port" => "6382" } end end def test_sentinel_command_get_master_by_name handler = lambda do |id| { :sentinel => lambda do |command, *args| ["127.0.0.1", "6381"] end } end RedisMock.start(handler.call(:s1)) do |port| redis = Redis.new(:host => "127.0.0.1", :port => port) result = redis.sentinel('get-master-addr-by-name', 'master1') assert_equal result, ["127.0.0.1", "6381"] end end def test_sentinel_command_ckquorum handler = lambda do |id| { :sentinel => lambda do |command, *args| "+OK 2 usable Sentinels. Quorum and failover authorization can be reached" end } end RedisMock.start(handler.call(:s1)) do |port| redis = Redis.new(:host => "127.0.0.1", :port => port) result = redis.sentinel('ckquorum', 'master1') assert_equal result, "OK 2 usable Sentinels. Quorum and failover authorization can be reached" end end end redis-3.2.2/test/unknown_commands_test.rb0000644000004100000410000000042712637240307020606 0ustar www-datawww-data# encoding: UTF-8 require File.expand_path("helper", File.dirname(__FILE__)) class TestUnknownCommands < Test::Unit::TestCase include Helper::Client def test_should_try_to_work assert_raise Redis::CommandError do r.not_yet_implemented_command end end end redis-3.2.2/test/distributed_commands_on_lists_test.rb0000644000004100000410000000074412637240307023345 0ustar www-datawww-data# encoding: UTF-8 require File.expand_path("helper", File.dirname(__FILE__)) require "lint/lists" class TestDistributedCommandsOnLists < Test::Unit::TestCase include Helper::Distributed include Lint::Lists def test_rpoplpush assert_raise Redis::Distributed::CannotDistribute do r.rpoplpush("foo", "bar") end end def test_brpoplpush assert_raise Redis::Distributed::CannotDistribute do r.brpoplpush("foo", "bar", :timeout => 1) end end end redis-3.2.2/test/pipelining_commands_test.rb0000644000004100000410000001171312637240307021245 0ustar www-datawww-data# encoding: UTF-8 require File.expand_path("helper", File.dirname(__FILE__)) class TestPipeliningCommands < Test::Unit::TestCase include Helper::Client def test_bulk_commands r.pipelined do r.lpush "foo", "s1" r.lpush "foo", "s2" end assert_equal 2, r.llen("foo") assert_equal "s2", r.lpop("foo") assert_equal "s1", r.lpop("foo") end def test_multi_bulk_commands r.pipelined do r.mset("foo", "s1", "bar", "s2") r.mset("baz", "s3", "qux", "s4") end assert_equal "s1", r.get("foo") assert_equal "s2", r.get("bar") assert_equal "s3", r.get("baz") assert_equal "s4", r.get("qux") end def test_bulk_and_multi_bulk_commands_mixed r.pipelined do r.lpush "foo", "s1" r.lpush "foo", "s2" r.mset("baz", "s3", "qux", "s4") end assert_equal 2, r.llen("foo") assert_equal "s2", r.lpop("foo") assert_equal "s1", r.lpop("foo") assert_equal "s3", r.get("baz") assert_equal "s4", r.get("qux") end def test_multi_bulk_and_bulk_commands_mixed r.pipelined do r.mset("baz", "s3", "qux", "s4") r.lpush "foo", "s1" r.lpush "foo", "s2" end assert_equal 2, r.llen("foo") assert_equal "s2", r.lpop("foo") assert_equal "s1", r.lpop("foo") assert_equal "s3", r.get("baz") assert_equal "s4", r.get("qux") end def test_pipelined_with_an_empty_block assert_nothing_raised do r.pipelined do end end assert_equal 0, r.dbsize end def test_returning_the_result_of_a_pipeline result = r.pipelined do r.set "foo", "bar" r.get "foo" r.get "bar" end assert_equal ["OK", "bar", nil], result end def test_assignment_of_results_inside_the_block r.pipelined do @first = r.sadd("foo", 1) @second = r.sadd("foo", 1) end assert_equal true, @first.value assert_equal false, @second.value end # Although we could support accessing the values in these futures, # it doesn't make a lot of sense. def test_assignment_of_results_inside_the_block_with_errors assert_raise(Redis::CommandError) do r.pipelined do r.doesnt_exist @first = r.sadd("foo", 1) @second = r.sadd("foo", 1) end end assert_raise(Redis::FutureNotReady) { @first.value } assert_raise(Redis::FutureNotReady) { @second.value } end def test_assignment_of_results_inside_a_nested_block r.pipelined do @first = r.sadd("foo", 1) r.pipelined do @second = r.sadd("foo", 1) end end assert_equal true, @first.value assert_equal false, @second.value end def test_futures_raise_when_confused_with_something_else r.pipelined do @result = r.sadd("foo", 1) end assert_raise(NoMethodError) { @result.to_s } end def test_futures_raise_when_trying_to_access_their_values_too_early r.pipelined do assert_raise(Redis::FutureNotReady) do r.sadd("foo", 1).value end end end def test_futures_can_be_identified r.pipelined do @result = r.sadd("foo", 1) end assert_equal true, @result.is_a?(Redis::Future) if defined?(::BasicObject) assert_equal true, @result.is_a?(::BasicObject) end assert_equal Redis::Future, @result.class end def test_returning_the_result_of_an_empty_pipeline result = r.pipelined do end assert_equal [], result end def test_nesting_pipeline_blocks r.pipelined do r.set("foo", "s1") r.pipelined do r.set("bar", "s2") end end assert_equal "s1", r.get("foo") assert_equal "s2", r.get("bar") end def test_info_in_a_pipeline_returns_hash result = r.pipelined do r.info end assert result.first.kind_of?(Hash) end def test_config_get_in_a_pipeline_returns_hash result = r.pipelined do r.config(:get, "*") end assert result.first.kind_of?(Hash) end def test_hgetall_in_a_pipeline_returns_hash r.hmset("hash", "field", "value") result = r.pipelined do r.hgetall("hash") end assert_equal result.first, { "field" => "value" } end def test_keys_in_a_pipeline r.set("key", "value") result = r.pipelined do r.keys("*") end assert_equal ["key"], result.first end def test_pipeline_yields_a_connection r.pipelined do |p| p.set("foo", "bar") end assert_equal "bar", r.get("foo") end def test_pipeline_select r.select 1 r.set("db", "1") r.pipelined do |p| p.select 2 p.set("db", "2") end r.select 1 assert_equal "1", r.get("db") r.select 2 assert_equal "2", r.get("db") end def test_pipeline_select_client_db r.select 1 r.pipelined do |p2| p2.select 2 end assert_equal 2, r.client.db end def test_nested_pipeline_select_client_db r.select 1 r.pipelined do |p2| p2.select 2 p2.pipelined do |p3| p3.select 3 end end assert_equal 3, r.client.db end end redis-3.2.2/test/commands_on_hashes_test.rb0000644000004100000410000000071312637240307021054 0ustar www-datawww-data# encoding: UTF-8 require File.expand_path("helper", File.dirname(__FILE__)) require "lint/hashes" class TestCommandsOnHashes < Test::Unit::TestCase include Helper::Client include Lint::Hashes def test_mapped_hmget_in_a_pipeline_returns_hash r.hset("foo", "f1", "s1") r.hset("foo", "f2", "s2") result = r.pipelined do r.mapped_hmget("foo", "f1", "f2") end assert_equal result[0], { "f1" => "s1", "f2" => "s2" } end end redis-3.2.2/test/persistence_control_commands_test.rb0000644000004100000410000000107312637240307023171 0ustar www-datawww-data# encoding: UTF-8 require File.expand_path("helper", File.dirname(__FILE__)) class TestPersistenceControlCommands < Test::Unit::TestCase include Helper::Client def test_save redis_mock(:save => lambda { "+SAVE" }) do |redis| assert_equal "SAVE", redis.save end end def test_bgsave redis_mock(:bgsave => lambda { "+BGSAVE" }) do |redis| assert_equal "BGSAVE", redis.bgsave end end def test_lastsave redis_mock(:lastsave => lambda { "+LASTSAVE" }) do |redis| assert_equal "LASTSAVE", redis.lastsave end end end redis-3.2.2/test/distributed_sorting_test.rb0000644000004100000410000000064212637240307021314 0ustar www-datawww-data# encoding: UTF-8 require File.expand_path("helper", File.dirname(__FILE__)) class TestDistributedSorting < Test::Unit::TestCase include Helper::Distributed def test_sort assert_raise(Redis::Distributed::CannotDistribute) do r.set("foo:1", "s1") r.set("foo:2", "s2") r.rpush("bar", "1") r.rpush("bar", "2") r.sort("bar", :get => "foo:*", :limit => [0, 1]) end end end redis-3.2.2/test/connection_handling_test.rb0000644000004100000410000001301312637240307021224 0ustar www-datawww-data# encoding: UTF-8 require File.expand_path("helper", File.dirname(__FILE__)) class TestConnectionHandling < Test::Unit::TestCase include Helper::Client def test_auth commands = { :auth => lambda { |password| $auth = password; "+OK" }, :get => lambda { |key| $auth == "secret" ? "$3\r\nbar" : "$-1" }, } redis_mock(commands, :password => "secret") do |redis| assert_equal "bar", redis.get("foo") end end def test_id commands = { :client => lambda { |cmd, name| $name = [cmd, name]; "+OK" }, :ping => lambda { "+PONG" }, } redis_mock(commands, :id => "client-name") do |redis| assert_equal "PONG", redis.ping end assert_equal ["setname","client-name"], $name end def test_ping assert_equal "PONG", r.ping end def test_select r.set "foo", "bar" r.select 14 assert_equal nil, r.get("foo") r.client.disconnect assert_equal nil, r.get("foo") end def test_quit r.quit assert !r.client.connected? end def test_disconnect quit = 0 commands = { :quit => lambda do quit += 1 "+OK" end } redis_mock(commands) do |redis| assert_equal 0, quit redis.quit assert_equal 1, quit redis.ping redis.disconnect! assert_equal 1, quit assert !redis.connected? end end def test_shutdown commands = { :shutdown => lambda { :exit } } redis_mock(commands) do |redis| # SHUTDOWN does not reply: test that it does not raise here. assert_equal nil, redis.shutdown end end def test_shutdown_with_error connections = 0 commands = { :select => lambda { |*_| connections += 1; "+OK\r\n" }, :connections => lambda { ":#{connections}\r\n" }, :shutdown => lambda { "-ERR could not shutdown\r\n" } } redis_mock(commands) do |redis| connections = redis.connections # SHUTDOWN replies with an error: test that it gets raised assert_raise Redis::CommandError do redis.shutdown end # The connection should remain in tact assert_equal connections, redis.connections end end def test_shutdown_from_pipeline commands = { :shutdown => lambda { :exit } } redis_mock(commands) do |redis| result = redis.pipelined do redis.shutdown end assert_equal nil, result assert !redis.client.connected? end end def test_shutdown_with_error_from_pipeline connections = 0 commands = { :select => lambda { |*_| connections += 1; "+OK\r\n" }, :connections => lambda { ":#{connections}\r\n" }, :shutdown => lambda { "-ERR could not shutdown\r\n" } } redis_mock(commands) do |redis| connections = redis.connections # SHUTDOWN replies with an error: test that it gets raised assert_raise Redis::CommandError do redis.pipelined do redis.shutdown end end # The connection should remain in tact assert_equal connections, redis.connections end end def test_shutdown_from_multi_exec commands = { :multi => lambda { "+OK\r\n" }, :shutdown => lambda { "+QUEUED\r\n" }, :exec => lambda { :exit } } redis_mock(commands) do |redis| result = redis.multi do redis.shutdown end assert_equal nil, result assert !redis.client.connected? end end def test_shutdown_with_error_from_multi_exec connections = 0 commands = { :select => lambda { |*_| connections += 1; "+OK\r\n" }, :connections => lambda { ":#{connections}\r\n" }, :multi => lambda { "+OK\r\n" }, :shutdown => lambda { "+QUEUED\r\n" }, :exec => lambda { "*1\r\n-ERR could not shutdown\r\n" } } redis_mock(commands) do |redis| connections = redis.connections # SHUTDOWN replies with an error: test that it gets returned # We should test for Redis::CommandError here, but hiredis doesn't yet do # custom error classes. err = nil begin redis.multi { redis.shutdown } rescue => err end assert err.kind_of?(StandardError) # The connection should remain intact assert_equal connections, redis.connections end end def test_slaveof redis_mock(:slaveof => lambda { |host, port| "+SLAVEOF #{host} #{port}" }) do |redis| assert_equal "SLAVEOF somehost 6381", redis.slaveof("somehost", 6381) end end def test_bgrewriteaof redis_mock(:bgrewriteaof => lambda { "+BGREWRITEAOF" }) do |redis| assert_equal "BGREWRITEAOF", redis.bgrewriteaof end end def test_config_get assert r.config(:get, "*")["timeout"] != nil config = r.config(:get, "timeout") assert_equal ["timeout"], config.keys assert config.values.compact.size > 0 end def test_config_set begin assert_equal "OK", r.config(:set, "timeout", 200) assert_equal "200", r.config(:get, "*")["timeout"] assert_equal "OK", r.config(:set, "timeout", 100) assert_equal "100", r.config(:get, "*")["timeout"] ensure r.config :set, "timeout", 300 end end driver(:ruby, :hiredis) do def test_consistency_on_multithreaded_env t = nil commands = { :set => lambda { |key, value| t.kill; "+OK\r\n" }, :incr => lambda { |key| ":1\r\n" }, } redis_mock(commands) do |redis| t = Thread.new do redis.set("foo", "bar") end t.join assert_equal 1, redis.incr("baz") end end end end redis-3.2.2/test/distributed_commands_on_value_types_test.rb0000644000004100000410000000344412637240307024547 0ustar www-datawww-data# encoding: UTF-8 require File.expand_path("helper", File.dirname(__FILE__)) require "lint/value_types" class TestDistributedCommandsOnValueTypes < Test::Unit::TestCase include Helper::Distributed include Lint::ValueTypes def test_del r.set "foo", "s1" r.set "bar", "s2" r.set "baz", "s3" assert_equal ["bar", "baz", "foo"], r.keys("*").sort assert_equal 1, r.del("foo") assert_equal ["bar", "baz"], r.keys("*").sort assert_equal 2, r.del("bar", "baz") assert_equal [], r.keys("*").sort end def test_del_with_array_argument r.set "foo", "s1" r.set "bar", "s2" r.set "baz", "s3" assert_equal ["bar", "baz", "foo"], r.keys("*").sort assert_equal 1, r.del(["foo"]) assert_equal ["bar", "baz"], r.keys("*").sort assert_equal 2, r.del(["bar", "baz"]) assert_equal [], r.keys("*").sort end def test_randomkey assert_raise Redis::Distributed::CannotDistribute do r.randomkey end end def test_rename assert_raise Redis::Distributed::CannotDistribute do r.set("foo", "s1") r.rename "foo", "bar" end assert_equal "s1", r.get("foo") assert_equal nil, r.get("bar") end def test_renamenx assert_raise Redis::Distributed::CannotDistribute do r.set("foo", "s1") r.rename "foo", "bar" end assert_equal "s1", r.get("foo") assert_equal nil , r.get("bar") end def test_dbsize assert_equal [0], r.dbsize r.set("foo", "s1") assert_equal [1], r.dbsize end def test_flushdb r.set("foo", "s1") r.set("bar", "s2") assert_equal [2], r.dbsize r.flushdb assert_equal [0], r.dbsize end def test_migrate r.set("foo", "s1") assert_raise Redis::Distributed::CannotDistribute do r.migrate("foo", {}) end end end redis-3.2.2/test/commands_on_hyper_log_log_test.rb0000644000004100000410000000066112637240307022434 0ustar www-datawww-data# encoding: UTF-8 require File.expand_path("helper", File.dirname(__FILE__)) require "lint/hyper_log_log" class TestCommandsOnHyperLogLog < Test::Unit::TestCase include Helper::Client include Lint::HyperLogLog def test_pfmerge target_version "2.8.9" do r.pfadd "foo", "s1" r.pfadd "bar", "s2" assert_equal true, r.pfmerge("res", "foo", "bar") assert_equal 2, r.pfcount("res") end end endredis-3.2.2/test/publish_subscribe_test.rb0000644000004100000410000001307012637240307020733 0ustar www-datawww-data# encoding: UTF-8 require File.expand_path("helper", File.dirname(__FILE__)) class TestPublishSubscribe < Test::Unit::TestCase include Helper::Client class TestError < StandardError end def test_subscribe_and_unsubscribe @subscribed = false @unsubscribed = false wire = Wire.new do r.subscribe("foo") do |on| on.subscribe do |channel, total| @subscribed = true @t1 = total end on.message do |channel, message| if message == "s1" r.unsubscribe @message = message end end on.unsubscribe do |channel, total| @unsubscribed = true @t2 = total end end end # Wait until the subscription is active before publishing Wire.pass while !@subscribed Redis.new(OPTIONS).publish("foo", "s1") wire.join assert @subscribed assert_equal 1, @t1 assert @unsubscribed assert_equal 0, @t2 assert_equal "s1", @message end def test_psubscribe_and_punsubscribe @subscribed = false @unsubscribed = false wire = Wire.new do r.psubscribe("f*") do |on| on.psubscribe do |pattern, total| @subscribed = true @t1 = total end on.pmessage do |pattern, channel, message| if message == "s1" r.punsubscribe @message = message end end on.punsubscribe do |pattern, total| @unsubscribed = true @t2 = total end end end # Wait until the subscription is active before publishing Wire.pass while !@subscribed Redis.new(OPTIONS).publish("foo", "s1") wire.join assert @subscribed assert_equal 1, @t1 assert @unsubscribed assert_equal 0, @t2 assert_equal "s1", @message end def test_pubsub_with_numpat_subcommand target_version("2.8.0") do @subscribed = false wire = Wire.new do r.psubscribe("f*") do |on| on.psubscribe { |channel, total| @subscribed = true } on.pmessage { |pattern, channel, message| r.punsubscribe } end end Wire.pass while !@subscribed redis = Redis.new(OPTIONS) numpat_result = redis.pubsub(:numpat) redis.publish("foo", "s1") wire.join assert_equal redis.pubsub(:numpat), 0 assert_equal numpat_result, 1 end end def test_pubsub_with_channels_and_numsub_subcommnads target_version("2.8.0") do @subscribed = false wire = Wire.new do r.subscribe("foo") do |on| on.subscribe { |channel, total| @subscribed = true } on.message { |channel, message| r.unsubscribe } end end Wire.pass while !@subscribed redis = Redis.new(OPTIONS) channels_result = redis.pubsub(:channels) numsub_result = redis.pubsub(:numsub, 'foo', 'boo') redis.publish("foo", "s1") wire.join assert_equal channels_result, ['foo'] assert_equal numsub_result, ['foo', 1, 'boo', 0] end end def test_subscribe_connection_usable_after_raise @subscribed = false wire = Wire.new do begin r.subscribe("foo") do |on| on.subscribe do |channel, total| @subscribed = true end on.message do |channel, message| raise TestError end end rescue TestError end end # Wait until the subscription is active before publishing Wire.pass while !@subscribed Redis.new(OPTIONS).publish("foo", "s1") wire.join assert_equal "PONG", r.ping end def test_psubscribe_connection_usable_after_raise @subscribed = false wire = Wire.new do begin r.psubscribe("f*") do |on| on.psubscribe do |pattern, total| @subscribed = true end on.pmessage do |pattern, channel, message| raise TestError end end rescue TestError end end # Wait until the subscription is active before publishing Wire.pass while !@subscribed Redis.new(OPTIONS).publish("foo", "s1") wire.join assert_equal "PONG", r.ping end def test_subscribe_within_subscribe @channels = [] wire = Wire.new do r.subscribe("foo") do |on| on.subscribe do |channel, total| @channels << channel r.subscribe("bar") if channel == "foo" r.unsubscribe if channel == "bar" end end end wire.join assert_equal ["foo", "bar"], @channels end def test_other_commands_within_a_subscribe assert_raise Redis::CommandError do r.subscribe("foo") do |on| on.subscribe do |channel, total| r.set("bar", "s2") end end end end def test_subscribe_without_a_block assert_raise LocalJumpError do r.subscribe("foo") end end def test_unsubscribe_without_a_subscribe assert_raise RuntimeError do r.unsubscribe end assert_raise RuntimeError do r.punsubscribe end end def test_subscribe_past_a_timeout # For some reason, a thread here doesn't reproduce the issue. sleep = %{sleep #{OPTIONS[:timeout] * 2}} publish = %{ruby -rsocket -e 't=TCPSocket.new("127.0.0.1",#{OPTIONS[:port]});t.write("publish foo bar\\r\\n");t.read(4);t.close'} cmd = [sleep, publish].join("; ") IO.popen(cmd, "r+") do |pipe| received = false r.subscribe "foo" do |on| on.message do |channel, message| received = true r.unsubscribe end end assert received end end end redis-3.2.2/test/commands_on_sets_test.rb0000644000004100000410000000272312637240307020562 0ustar www-datawww-data# encoding: UTF-8 require File.expand_path("helper", File.dirname(__FILE__)) require "lint/sets" class TestCommandsOnSets < Test::Unit::TestCase include Helper::Client include Lint::Sets def test_smove r.sadd "foo", "s1" r.sadd "bar", "s2" assert r.smove("foo", "bar", "s1") assert r.sismember("bar", "s1") end def test_sinter r.sadd "foo", "s1" r.sadd "foo", "s2" r.sadd "bar", "s2" assert_equal ["s2"], r.sinter("foo", "bar") end def test_sinterstore r.sadd "foo", "s1" r.sadd "foo", "s2" r.sadd "bar", "s2" r.sinterstore("baz", "foo", "bar") assert_equal ["s2"], r.smembers("baz") end def test_sunion r.sadd "foo", "s1" r.sadd "foo", "s2" r.sadd "bar", "s2" r.sadd "bar", "s3" assert_equal ["s1", "s2", "s3"], r.sunion("foo", "bar").sort end def test_sunionstore r.sadd "foo", "s1" r.sadd "foo", "s2" r.sadd "bar", "s2" r.sadd "bar", "s3" r.sunionstore("baz", "foo", "bar") assert_equal ["s1", "s2", "s3"], r.smembers("baz").sort end def test_sdiff r.sadd "foo", "s1" r.sadd "foo", "s2" r.sadd "bar", "s2" r.sadd "bar", "s3" assert_equal ["s1"], r.sdiff("foo", "bar") assert_equal ["s3"], r.sdiff("bar", "foo") end def test_sdiffstore r.sadd "foo", "s1" r.sadd "foo", "s2" r.sadd "bar", "s2" r.sadd "bar", "s3" r.sdiffstore("baz", "foo", "bar") assert_equal ["s1"], r.smembers("baz") end end redis-3.2.2/test/remote_server_control_commands_test.rb0000644000004100000410000000437312637240307023534 0ustar www-datawww-data# encoding: UTF-8 require File.expand_path("helper", File.dirname(__FILE__)) class TestRemoteServerControlCommands < Test::Unit::TestCase include Helper::Client def test_info keys = [ "redis_version", "uptime_in_seconds", "uptime_in_days", "connected_clients", "used_memory", "total_connections_received", "total_commands_processed", ] info = r.info keys.each do |k| msg = "expected #info to include #{k}" assert info.keys.include?(k), msg end end def test_info_commandstats target_version "2.5.7" do r.config(:resetstat) r.ping result = r.info(:commandstats) assert_equal "1", result["ping"]["calls"] end end def test_monitor_redis_lt_2_5_0 return unless version < "2.5.0" log = [] wire = Wire.new do Redis.new(OPTIONS).monitor do |line| log << line break if log.size == 3 end end Wire.pass while log.empty? # Faster than sleep r.set "foo", "s1" wire.join assert log[-1][%q{(db 15) "set" "foo" "s1"}] end def test_monitor_redis_gte_2_5_0 return unless version >= "2.5.0" log = [] wire = Wire.new do Redis.new(OPTIONS).monitor do |line| log << line break if line =~ /set/ end end Wire.pass while log.empty? # Faster than sleep r.set "foo", "s1" wire.join assert log[-1] =~ /\b15\b.* "set" "foo" "s1"/ end def test_monitor_returns_value_for_break result = r.monitor do |line| break line end assert_equal "OK", result end def test_echo assert_equal "foo bar baz\n", r.echo("foo bar baz\n") end def test_debug r.set "foo", "s1" assert r.debug(:object, "foo").kind_of?(String) end def test_object r.lpush "list", "value" assert_equal 1, r.object(:refcount, "list") encoding = r.object(:encoding, "list") assert "ziplist" == encoding || "quicklist" == encoding, "Wrong encoding for list" assert r.object(:idletime, "list").kind_of?(Fixnum) end def test_sync redis_mock(:sync => lambda { "+OK" }) do |redis| assert_equal "OK", redis.sync end end def test_slowlog r.slowlog(:reset) result = r.slowlog(:len) assert_equal 0, result end end redis-3.2.2/test/sentinel_test.rb0000644000004100000410000001451012637240307017045 0ustar www-datawww-data# encoding: UTF-8 require File.expand_path("helper", File.dirname(__FILE__)) class SentinalTest < Test::Unit::TestCase include Helper::Client def test_sentinel_connection sentinels = [{:host => "127.0.0.1", :port => 26381}, {:host => "127.0.0.1", :port => 26382}] commands = { :s1 => [], :s2 => [], } handler = lambda do |id| { :sentinel => lambda do |command, *args| commands[id] << [command, *args] ["127.0.0.1", "6381"] end } end RedisMock.start(handler.call(:s1)) do |s1_port| RedisMock.start(handler.call(:s2)) do |s2_port| sentinels[0][:port] = s1_port sentinels[1][:port] = s2_port redis = Redis.new(:url => "redis://master1", :sentinels => sentinels, :role => :master) assert redis.ping end end assert_equal commands[:s1], [%w[get-master-addr-by-name master1]] assert_equal commands[:s2], [] end def test_sentinel_failover sentinels = [{:host => "127.0.0.1", :port => 26381}, {:host => "127.0.0.1", :port => 26382}] commands = { :s1 => [], :s2 => [], } s1 = { :sentinel => lambda do |command, *args| commands[:s1] << [command, *args] "$-1" # Nil end } s2 = { :sentinel => lambda do |command, *args| commands[:s2] << [command, *args] ["127.0.0.1", "6381"] end } RedisMock.start(s1) do |s1_port| RedisMock.start(s2) do |s2_port| sentinels[0][:port] = s1_port sentinels[1][:port] = s2_port redis = Redis.new(:url => "redis://master1", :sentinels => sentinels, :role => :master) assert redis.ping end end assert_equal commands[:s1], [%w[get-master-addr-by-name master1]] assert_equal commands[:s2], [%w[get-master-addr-by-name master1]] end def test_sentinel_failover_prioritize_healthy_sentinel sentinels = [{:host => "127.0.0.1", :port => 26381}, {:host => "127.0.0.1", :port => 26382}] commands = { :s1 => [], :s2 => [], } s1 = { :sentinel => lambda do |command, *args| commands[:s1] << [command, *args] "$-1" # Nil end } s2 = { :sentinel => lambda do |command, *args| commands[:s2] << [command, *args] ["127.0.0.1", "6381"] end } RedisMock.start(s1) do |s1_port| RedisMock.start(s2) do |s2_port| sentinels[0][:port] = s1_port sentinels[1][:port] = s2_port redis = Redis.new(:url => "redis://master1", :sentinels => sentinels, :role => :master) assert redis.ping redis.quit assert redis.ping end end assert_equal commands[:s1], [%w[get-master-addr-by-name master1]] assert_equal commands[:s2], [%w[get-master-addr-by-name master1], %w[get-master-addr-by-name master1]] end def test_sentinel_with_non_sentinel_options sentinels = [{:host => "127.0.0.1", :port => 26381}] commands = { :s1 => [], :m1 => [] } sentinel = lambda do |port| { :auth => lambda do |pass| commands[:s1] << ["auth", pass] "-ERR unknown command 'auth'" end, :select => lambda do |db| commands[:s1] << ["select", db] "-ERR unknown command 'select'" end, :sentinel => lambda do |command, *args| commands[:s1] << [command, *args] ["127.0.0.1", port.to_s] end } end master = { :auth => lambda do |pass| commands[:m1] << ["auth", pass] "+OK" end, :role => lambda do commands[:m1] << ["role"] ["master"] end } RedisMock.start(master) do |master_port| RedisMock.start(sentinel.call(master_port)) do |sen_port| sentinels[0][:port] = sen_port redis = Redis.new(:url => "redis://:foo@master1/15", :sentinels => sentinels, :role => :master) assert redis.ping end end assert_equal [%w[get-master-addr-by-name master1]], commands[:s1] assert_equal [%w[auth foo], %w[role]], commands[:m1] end def test_sentinel_role_mismatch sentinels = [{:host => "127.0.0.1", :port => 26381}] sentinel = lambda do |port| { :sentinel => lambda do |command, *args| ["127.0.0.1", port.to_s] end } end master = { :role => lambda do ["slave"] end } ex = assert_raise(Redis::ConnectionError) do RedisMock.start(master) do |master_port| RedisMock.start(sentinel.call(master_port)) do |sen_port| sentinels[0][:port] = sen_port redis = Redis.new(:url => "redis://master1", :sentinels => sentinels, :role => :master) assert redis.ping end end end assert_match(/Instance role mismatch/, ex.message) end def test_sentinel_retries sentinels = [{:host => "127.0.0.1", :port => 26381}, {:host => "127.0.0.1", :port => 26382}] connections = [] handler = lambda do |id, port| { :sentinel => lambda do |command, *args| connections << id if connections.count(id) < 2 :close else ["127.0.0.1", port.to_s] end end } end master = { :role => lambda do ["master"] end } RedisMock.start(master) do |master_port| RedisMock.start(handler.call(:s1, master_port)) do |s1_port| RedisMock.start(handler.call(:s2, master_port)) do |s2_port| sentinels[0][:port] = s1_port sentinels[1][:port] = s2_port redis = Redis.new(:url => "redis://master1", :sentinels => sentinels, :role => :master, :reconnect_attempts => 1) assert redis.ping end end end assert_equal [:s1, :s2, :s1], connections connections.clear ex = assert_raise(Redis::CannotConnectError) do RedisMock.start(master) do |master_port| RedisMock.start(handler.call(:s1, master_port)) do |s1_port| RedisMock.start(handler.call(:s2, master_port)) do |s2_port| redis = Redis.new(:url => "redis://master1", :sentinels => sentinels, :role => :master, :reconnect_attempts => 0) assert redis.ping end end end end assert_match(/No sentinels available/, ex.message) end end redis-3.2.2/test/commands_on_value_types_test.rb0000644000004100000410000000577612637240307022157 0ustar www-datawww-data# encoding: UTF-8 require File.expand_path("helper", File.dirname(__FILE__)) require "lint/value_types" class TestCommandsOnValueTypes < Test::Unit::TestCase include Helper::Client include Lint::ValueTypes def test_del r.set "foo", "s1" r.set "bar", "s2" r.set "baz", "s3" assert_equal ["bar", "baz", "foo"], r.keys("*").sort assert_equal 1, r.del("foo") assert_equal ["bar", "baz"], r.keys("*").sort assert_equal 2, r.del("bar", "baz") assert_equal [], r.keys("*").sort end def test_del_with_array_argument r.set "foo", "s1" r.set "bar", "s2" r.set "baz", "s3" assert_equal ["bar", "baz", "foo"], r.keys("*").sort assert_equal 1, r.del(["foo"]) assert_equal ["bar", "baz"], r.keys("*").sort assert_equal 2, r.del(["bar", "baz"]) assert_equal [], r.keys("*").sort end def test_randomkey assert r.randomkey.to_s.empty? r.set("foo", "s1") assert_equal "foo", r.randomkey r.set("bar", "s2") 4.times do assert ["foo", "bar"].include?(r.randomkey) end end def test_rename r.set("foo", "s1") r.rename "foo", "bar" assert_equal "s1", r.get("bar") assert_equal nil, r.get("foo") end def test_renamenx r.set("foo", "s1") r.set("bar", "s2") assert_equal false, r.renamenx("foo", "bar") assert_equal "s1", r.get("foo") assert_equal "s2", r.get("bar") end def test_dbsize assert_equal 0, r.dbsize r.set("foo", "s1") assert_equal 1, r.dbsize end def test_flushdb r.set("foo", "s1") r.set("bar", "s2") assert_equal 2, r.dbsize r.flushdb assert_equal 0, r.dbsize end def test_flushall redis_mock(:flushall => lambda { "+FLUSHALL" }) do |redis| assert_equal "FLUSHALL", redis.flushall end end def test_migrate redis_mock(:migrate => lambda { |*args| args }) do |redis| options = { :host => "127.0.0.1", :port => 1234 } ex = assert_raise(RuntimeError) do redis.migrate("foo", options.reject { |key, _| key == :host }) end assert ex.message =~ /host not specified/ ex = assert_raise(RuntimeError) do redis.migrate("foo", options.reject { |key, _| key == :port }) end assert ex.message =~ /port not specified/ default_db = redis.client.db.to_i default_timeout = redis.client.timeout.to_i # Test defaults actual = redis.migrate("foo", options) expected = ["127.0.0.1", "1234", "foo", default_db.to_s, default_timeout.to_s] assert_equal expected, actual # Test db override actual = redis.migrate("foo", options.merge(:db => default_db + 1)) expected = ["127.0.0.1", "1234", "foo", (default_db + 1).to_s, default_timeout.to_s] assert_equal expected, actual # Test timeout override actual = redis.migrate("foo", options.merge(:timeout => default_timeout + 1)) expected = ["127.0.0.1", "1234", "foo", default_db.to_s, (default_timeout + 1).to_s] assert_equal expected, actual end end end redis-3.2.2/test/distributed_publish_subscribe_test.rb0000644000004100000410000000367612637240307023350 0ustar www-datawww-data# encoding: UTF-8 require File.expand_path("helper", File.dirname(__FILE__)) class TestDistributedPublishSubscribe < Test::Unit::TestCase include Helper::Distributed def test_subscribe_and_unsubscribe assert_raise Redis::Distributed::CannotDistribute do r.subscribe("foo", "bar") { } end assert_raise Redis::Distributed::CannotDistribute do r.subscribe("{qux}foo", "bar") { } end end def test_subscribe_and_unsubscribe_with_tags @subscribed = false @unsubscribed = false wire = Wire.new do r.subscribe("foo") do |on| on.subscribe do |channel, total| @subscribed = true @t1 = total end on.message do |channel, message| if message == "s1" r.unsubscribe @message = message end end on.unsubscribe do |channel, total| @unsubscribed = true @t2 = total end end end # Wait until the subscription is active before publishing Wire.pass while !@subscribed Redis::Distributed.new(NODES).publish("foo", "s1") wire.join assert @subscribed assert_equal 1, @t1 assert @unsubscribed assert_equal 0, @t2 assert_equal "s1", @message end def test_subscribe_within_subscribe @channels = [] wire = Wire.new do r.subscribe("foo") do |on| on.subscribe do |channel, total| @channels << channel r.subscribe("bar") if channel == "foo" r.unsubscribe if channel == "bar" end end end wire.join assert_equal ["foo", "bar"], @channels end def test_other_commands_within_a_subscribe assert_raise Redis::CommandError do r.subscribe("foo") do |on| on.subscribe do |channel, total| r.set("bar", "s2") end end end end def test_subscribe_without_a_block assert_raise LocalJumpError do r.subscribe("foo") end end end redis-3.2.2/test/thread_safety_test.rb0000644000004100000410000000114512637240307020046 0ustar www-datawww-data# encoding: UTF-8 require File.expand_path("helper", File.dirname(__FILE__)) class TestThreadSafety < Test::Unit::TestCase include Helper::Client driver(:ruby, :hiredis) do def test_thread_safety redis = Redis.new(OPTIONS) redis.set "foo", 1 redis.set "bar", 2 sample = 100 t1 = Thread.new do $foos = Array.new(sample) { redis.get "foo" } end t2 = Thread.new do $bars = Array.new(sample) { redis.get "bar" } end t1.join t2.join assert_equal ["1"], $foos.uniq assert_equal ["2"], $bars.uniq end end end redis-3.2.2/test/synchrony_driver.rb0000644000004100000410000000334012637240307017573 0ustar www-datawww-data# encoding: UTF-8 require 'em-synchrony' require 'em-synchrony/connection_pool' require 'redis' require 'redis/connection/synchrony' require File.expand_path("./helper", File.dirname(__FILE__)) PORT = 6381 OPTIONS = {:port => PORT, :db => 15} # # if running under Eventmachine + Synchrony (Ruby 1.9+), then # we can simulate the blocking API while performing the network # IO via the EM reactor. # EM.synchrony do r = Redis.new OPTIONS r.flushdb r.rpush "foo", "s1" r.rpush "foo", "s2" assert_equal 2, r.llen("foo") assert_equal "s2", r.rpop("foo") r.set("foo", "bar") assert_equal "bar", r.getset("foo", "baz") assert_equal "baz", r.get("foo") r.set("foo", "a") assert_equal 1, r.getbit("foo", 1) assert_equal 1, r.getbit("foo", 2) assert_equal 0, r.getbit("foo", 3) assert_equal 0, r.getbit("foo", 4) assert_equal 0, r.getbit("foo", 5) assert_equal 0, r.getbit("foo", 6) assert_equal 1, r.getbit("foo", 7) r.flushdb # command pipelining r.pipelined do r.lpush "foo", "s1" r.lpush "foo", "s2" end assert_equal 2, r.llen("foo") assert_equal "s2", r.lpop("foo") assert_equal "s1", r.lpop("foo") assert_equal "OK", r.client.call(:quit) assert_equal "PONG", r.ping rpool = EM::Synchrony::ConnectionPool.new(size: 5) { Redis.new OPTIONS } result = rpool.watch 'foo' do |rd| assert_kind_of Redis, rd rd.set "foo", "s1" rd.multi do |multi| multi.set "foo", "s2" end end assert_equal nil, result assert_equal "s1", rpool.get("foo") result = rpool.watch "foo" do |rd| assert_kind_of Redis, rd rd.multi do |multi| multi.set "foo", "s3" end end assert_equal ["OK"], result assert_equal "s3", rpool.get("foo") EM.stop end redis-3.2.2/test/fork_safety_test.rb0000644000004100000410000000317612637240307017546 0ustar www-datawww-data# encoding: UTF-8 require File.expand_path("helper", File.dirname(__FILE__)) class TestForkSafety < Test::Unit::TestCase include Helper::Client include Helper::Skipable driver(:ruby, :hiredis) do def test_fork_safety redis = Redis.new(OPTIONS) redis.set "foo", 1 child_pid = fork do begin # InheritedError triggers a reconnect, # so we need to disable reconnects to force # the exception bubble up redis.without_reconnect do redis.set "foo", 2 end rescue Redis::InheritedError exit 127 end end _, status = Process.wait2(child_pid) assert_equal 127, status.exitstatus assert_equal "1", redis.get("foo") rescue NotImplementedError => error raise unless error.message =~ /fork is not available/ return skip(error.message) end def test_fork_safety_with_enabled_inherited_socket redis = Redis.new(OPTIONS.merge(:inherit_socket => true)) redis.set "foo", 1 child_pid = fork do begin # InheritedError triggers a reconnect, # so we need to disable reconnects to force # the exception bubble up redis.without_reconnect do redis.set "foo", 2 end rescue Redis::InheritedError exit 127 end end _, status = Process.wait2(child_pid) assert_equal 0, status.exitstatus assert_equal "2", redis.get("foo") rescue NotImplementedError => error raise unless error.message =~ /fork is not available/ return skip(error.message) end end end redis-3.2.2/test/distributed_commands_on_hashes_test.rb0000644000004100000410000000033412637240307023455 0ustar www-datawww-data# encoding: UTF-8 require File.expand_path("helper", File.dirname(__FILE__)) require "lint/hashes" class TestDistributedCommandsOnHashes < Test::Unit::TestCase include Helper::Distributed include Lint::Hashes end redis-3.2.2/test/test.conf.erb0000644000004100000410000000026312637240307016235 0ustar www-datawww-datadir <%= REDIS_DIR %> pidfile <%= REDIS_PID %> port 6381 unixsocket <%= REDIS_SOCKET %> timeout 300 loglevel debug logfile <%= REDIS_LOG %> databases 16 daemonize yes redis-3.2.2/test/distributed_key_tags_test.rb0000644000004100000410000000253312637240307021436 0ustar www-datawww-data# encoding: UTF-8 require File.expand_path("helper", File.dirname(__FILE__)) class TestDistributedKeyTags < Test::Unit::TestCase include Helper include Helper::Distributed def test_hashes_consistently r1 = Redis::Distributed.new ["redis://localhost:#{PORT}/15", *NODES] r2 = Redis::Distributed.new ["redis://localhost:#{PORT}/15", *NODES] r3 = Redis::Distributed.new ["redis://localhost:#{PORT}/15", *NODES] assert_equal r1.node_for("foo").id, r2.node_for("foo").id assert_equal r1.node_for("foo").id, r3.node_for("foo").id end def test_allows_clustering_of_keys r = Redis::Distributed.new(NODES) r.add_node("redis://127.0.0.1:#{PORT}/14") r.flushdb 100.times do |i| r.set "{foo}users:#{i}", i end assert_equal [0, 100], r.nodes.map { |node| node.keys.size } end def test_distributes_keys_if_no_clustering_is_used r.add_node("redis://127.0.0.1:#{PORT}/14") r.flushdb r.set "users:1", 1 r.set "users:4", 4 assert_equal [1, 1], r.nodes.map { |node| node.keys.size } end def test_allows_passing_a_custom_tag_extractor r = Redis::Distributed.new(NODES, :tag => /^(.+?):/) r.add_node("redis://127.0.0.1:#{PORT}/14") r.flushdb 100.times do |i| r.set "foo:users:#{i}", i end assert_equal [0, 100], r.nodes.map { |node| node.keys.size } end end redis-3.2.2/test/support/0000755000004100000410000000000012637240307015353 5ustar www-datawww-dataredis-3.2.2/test/support/redis_mock.rb0000644000004100000410000000551312637240307020023 0ustar www-datawww-datarequire "socket" module RedisMock class Server def initialize(options = {}, &block) @server = TCPServer.new(options[:host] || "127.0.0.1", 0) @server.setsockopt(Socket::SOL_SOCKET, Socket::SO_REUSEADDR, true) end def port @server.addr[1] end def start(&block) @thread = Thread.new { run(&block) } end def shutdown @thread.kill end def run begin loop do session = @server.accept begin return if yield(session) == :exit ensure session.close end end rescue => ex $stderr.puts "Error running mock server: #{ex.message}" $stderr.puts ex.backtrace retry ensure @server.close end end end # Starts a mock Redis server in a thread. # # The server will use the lambda handler passed as argument to handle # connections. For example: # # handler = lambda { |session| session.close } # RedisMock.start_with_handler(handler) do # # Every connection will be closed immediately # end # def self.start_with_handler(blk, options = {}) server = Server.new(options) port = server.port begin server.start(&blk) yield(port) ensure server.shutdown end end # Starts a mock Redis server in a thread. # # The server will reply with a `+OK` to all commands, but you can # customize it by providing a hash. For example: # # RedisMock.start(:ping => lambda { "+PONG" }) do |port| # assert_equal "PONG", Redis.new(:port => port).ping # end # def self.start(commands, options = {}, &blk) handler = lambda do |session| while line = session.gets argv = Array.new(line[1..-3].to_i) do bytes = session.gets[1..-3].to_i arg = session.read(bytes) session.read(2) # Discard \r\n arg end command = argv.shift blk = commands[command.to_sym] blk ||= lambda { |*_| "+OK" } response = blk.call(*argv) # Convert a nil response to :close response ||= :close if response == :exit break :exit elsif response == :close break :close elsif response.is_a?(Array) session.write("*%d\r\n" % response.size) response.each do |resp| if resp.is_a?(Array) session.write("*%d\r\n" % resp.size) resp.each do |r| session.write("$%d\r\n%s\r\n" % [r.length, r]) end else session.write("$%d\r\n%s\r\n" % [resp.length, resp]) end end else session.write(response) session.write("\r\n") unless response.end_with?("\r\n") end end end start_with_handler(handler, options, &blk) end end redis-3.2.2/test/support/wire/0000755000004100000410000000000012637240307016321 5ustar www-datawww-dataredis-3.2.2/test/support/wire/synchrony.rb0000644000004100000410000000072012637240307020701 0ustar www-datawww-dataclass Wire < Fiber # We cannot run this fiber explicitly because EM schedules it. Resuming the # current fiber on the next tick to let the reactor do work. def self.pass f = Fiber.current EM.next_tick { f.resume } Fiber.yield end def self.sleep(sec) EM::Synchrony.sleep(sec) end def initialize(&blk) super # Schedule run in next tick EM.next_tick { resume } end def join self.class.pass while alive? end end redis-3.2.2/test/support/wire/thread.rb0000644000004100000410000000011212637240307020107 0ustar www-datawww-dataclass Wire < Thread def self.sleep(sec) Kernel.sleep(sec) end end redis-3.2.2/test/support/connection/0000755000004100000410000000000012637240307017512 5ustar www-datawww-dataredis-3.2.2/test/support/connection/ruby.rb0000644000004100000410000000003612637240307021017 0ustar www-datawww-datarequire "support/wire/thread" redis-3.2.2/test/support/connection/synchrony.rb0000644000004100000410000000027712637240307022101 0ustar www-datawww-datarequire "support/wire/synchrony" module Helper def around rv = nil EM.synchrony do begin rv = yield ensure EM.stop end end rv end end redis-3.2.2/test/support/connection/hiredis.rb0000644000004100000410000000003612637240307021465 0ustar www-datawww-datarequire "support/wire/thread" redis-3.2.2/test/distributed_persistence_control_commands_test.rb0000644000004100000410000000112112637240307025565 0ustar www-datawww-data# encoding: UTF-8 require File.expand_path("helper", File.dirname(__FILE__)) class TestDistributedPersistenceControlCommands < Test::Unit::TestCase include Helper::Distributed def test_save redis_mock(:save => lambda { "+SAVE" }) do |redis| assert_equal ["SAVE"], redis.save end end def test_bgsave redis_mock(:bgsave => lambda { "+BGSAVE" }) do |redis| assert_equal ["BGSAVE"], redis.bgsave end end def test_lastsave redis_mock(:lastsave => lambda { "+LASTSAVE" }) do |redis| assert_equal ["LASTSAVE"], redis.lastsave end end end redis-3.2.2/test/distributed_commands_requiring_clustering_test.rb0000644000004100000410000001046012637240307025753 0ustar www-datawww-data# encoding: UTF-8 require File.expand_path("helper", File.dirname(__FILE__)) class TestDistributedCommandsRequiringClustering < Test::Unit::TestCase include Helper::Distributed def test_rename r.set("{qux}foo", "s1") r.rename "{qux}foo", "{qux}bar" assert_equal "s1", r.get("{qux}bar") assert_equal nil, r.get("{qux}foo") end def test_renamenx r.set("{qux}foo", "s1") r.set("{qux}bar", "s2") assert_equal false, r.renamenx("{qux}foo", "{qux}bar") assert_equal "s1", r.get("{qux}foo") assert_equal "s2", r.get("{qux}bar") end def test_brpoplpush r.rpush "{qux}foo", "s1" r.rpush "{qux}foo", "s2" assert_equal "s2", r.brpoplpush("{qux}foo", "{qux}bar", :timeout => 1) assert_equal ["s2"], r.lrange("{qux}bar", 0, -1) end def test_rpoplpush r.rpush "{qux}foo", "s1" r.rpush "{qux}foo", "s2" assert_equal "s2", r.rpoplpush("{qux}foo", "{qux}bar") assert_equal ["s2"], r.lrange("{qux}bar", 0, -1) assert_equal "s1", r.rpoplpush("{qux}foo", "{qux}bar") assert_equal ["s1", "s2"], r.lrange("{qux}bar", 0, -1) end def test_smove r.sadd "{qux}foo", "s1" r.sadd "{qux}bar", "s2" assert r.smove("{qux}foo", "{qux}bar", "s1") assert r.sismember("{qux}bar", "s1") end def test_sinter r.sadd "{qux}foo", "s1" r.sadd "{qux}foo", "s2" r.sadd "{qux}bar", "s2" assert_equal ["s2"], r.sinter("{qux}foo", "{qux}bar") end def test_sinterstore r.sadd "{qux}foo", "s1" r.sadd "{qux}foo", "s2" r.sadd "{qux}bar", "s2" r.sinterstore("{qux}baz", "{qux}foo", "{qux}bar") assert_equal ["s2"], r.smembers("{qux}baz") end def test_sunion r.sadd "{qux}foo", "s1" r.sadd "{qux}foo", "s2" r.sadd "{qux}bar", "s2" r.sadd "{qux}bar", "s3" assert_equal ["s1", "s2", "s3"], r.sunion("{qux}foo", "{qux}bar").sort end def test_sunionstore r.sadd "{qux}foo", "s1" r.sadd "{qux}foo", "s2" r.sadd "{qux}bar", "s2" r.sadd "{qux}bar", "s3" r.sunionstore("{qux}baz", "{qux}foo", "{qux}bar") assert_equal ["s1", "s2", "s3"], r.smembers("{qux}baz").sort end def test_sdiff r.sadd "{qux}foo", "s1" r.sadd "{qux}foo", "s2" r.sadd "{qux}bar", "s2" r.sadd "{qux}bar", "s3" assert_equal ["s1"], r.sdiff("{qux}foo", "{qux}bar") assert_equal ["s3"], r.sdiff("{qux}bar", "{qux}foo") end def test_sdiffstore r.sadd "{qux}foo", "s1" r.sadd "{qux}foo", "s2" r.sadd "{qux}bar", "s2" r.sadd "{qux}bar", "s3" r.sdiffstore("{qux}baz", "{qux}foo", "{qux}bar") assert_equal ["s1"], r.smembers("{qux}baz") end def test_sort r.set("{qux}foo:1", "s1") r.set("{qux}foo:2", "s2") r.rpush("{qux}bar", "1") r.rpush("{qux}bar", "2") assert_equal ["s1"], r.sort("{qux}bar", :get => "{qux}foo:*", :limit => [0, 1]) assert_equal ["s2"], r.sort("{qux}bar", :get => "{qux}foo:*", :limit => [0, 1], :order => "desc alpha") end def test_sort_with_an_array_of_gets r.set("{qux}foo:1:a", "s1a") r.set("{qux}foo:1:b", "s1b") r.set("{qux}foo:2:a", "s2a") r.set("{qux}foo:2:b", "s2b") r.rpush("{qux}bar", "1") r.rpush("{qux}bar", "2") assert_equal [["s1a", "s1b"]], r.sort("{qux}bar", :get => ["{qux}foo:*:a", "{qux}foo:*:b"], :limit => [0, 1]) assert_equal [["s2a", "s2b"]], r.sort("{qux}bar", :get => ["{qux}foo:*:a", "{qux}foo:*:b"], :limit => [0, 1], :order => "desc alpha") assert_equal [["s1a", "s1b"], ["s2a", "s2b"]], r.sort("{qux}bar", :get => ["{qux}foo:*:a", "{qux}foo:*:b"]) end def test_sort_with_store r.set("{qux}foo:1", "s1") r.set("{qux}foo:2", "s2") r.rpush("{qux}bar", "1") r.rpush("{qux}bar", "2") r.sort("{qux}bar", :get => "{qux}foo:*", :store => "{qux}baz") assert_equal ["s1", "s2"], r.lrange("{qux}baz", 0, -1) end def test_bitop target_version "2.5.10" do r.set("{qux}foo", "a") r.set("{qux}bar", "b") r.bitop(:and, "{qux}foo&bar", "{qux}foo", "{qux}bar") assert_equal "\x60", r.get("{qux}foo&bar") r.bitop(:or, "{qux}foo|bar", "{qux}foo", "{qux}bar") assert_equal "\x63", r.get("{qux}foo|bar") r.bitop(:xor, "{qux}foo^bar", "{qux}foo", "{qux}bar") assert_equal "\x03", r.get("{qux}foo^bar") r.bitop(:not, "{qux}~foo", "{qux}foo") assert_equal "\x9E", r.get("{qux}~foo") end end end redis-3.2.2/test/helper_test.rb0000644000004100000410000000062412637240307016504 0ustar www-datawww-data# encoding: UTF-8 require File.expand_path("helper", File.dirname(__FILE__)) class TestHelper < Test::Unit::TestCase include Helper def test_version_comparison v = Version.new("2.0.1") assert v > "1" assert v > "2" assert v < "3" assert v < "10" assert v < "2.1" assert v < "2.0.2" assert v < "2.0.1.1" assert v < "2.0.10" assert v == "2.0.1" end end redis-3.2.2/test/sorting_test.rb0000644000004100000410000000304412637240307016711 0ustar www-datawww-data# encoding: UTF-8 require File.expand_path("helper", File.dirname(__FILE__)) class TestSorting < Test::Unit::TestCase include Helper::Client def test_sort r.set("foo:1", "s1") r.set("foo:2", "s2") r.rpush("bar", "1") r.rpush("bar", "2") assert_equal ["s1"], r.sort("bar", :get => "foo:*", :limit => [0, 1]) assert_equal ["s2"], r.sort("bar", :get => "foo:*", :limit => [0, 1], :order => "desc alpha") end def test_sort_with_an_array_of_gets r.set("foo:1:a", "s1a") r.set("foo:1:b", "s1b") r.set("foo:2:a", "s2a") r.set("foo:2:b", "s2b") r.rpush("bar", "1") r.rpush("bar", "2") assert_equal [["s1a", "s1b"]], r.sort("bar", :get => ["foo:*:a", "foo:*:b"], :limit => [0, 1]) assert_equal [["s2a", "s2b"]], r.sort("bar", :get => ["foo:*:a", "foo:*:b"], :limit => [0, 1], :order => "desc alpha") assert_equal [["s1a", "s1b"], ["s2a", "s2b"]], r.sort("bar", :get => ["foo:*:a", "foo:*:b"]) end def test_sort_with_store r.set("foo:1", "s1") r.set("foo:2", "s2") r.rpush("bar", "1") r.rpush("bar", "2") r.sort("bar", :get => "foo:*", :store => "baz") assert_equal ["s1", "s2"], r.lrange("baz", 0, -1) end def test_sort_with_an_array_of_gets_and_with_store r.set("foo:1:a", "s1a") r.set("foo:1:b", "s1b") r.set("foo:2:a", "s2a") r.set("foo:2:b", "s2b") r.rpush("bar", "1") r.rpush("bar", "2") r.sort("bar", :get => ["foo:*:a", "foo:*:b"], :store => 'baz') assert_equal ["s1a", "s1b", "s2a", "s2b"], r.lrange("baz", 0, -1) end end redis-3.2.2/test/distributed_commands_on_strings_test.rb0000644000004100000410000000241512637240307023675 0ustar www-datawww-data# encoding: UTF-8 require File.expand_path("helper", File.dirname(__FILE__)) require "lint/strings" class TestDistributedCommandsOnStrings < Test::Unit::TestCase include Helper::Distributed include Lint::Strings def test_mget assert_raise Redis::Distributed::CannotDistribute do r.mget("foo", "bar") end end def test_mget_mapped assert_raise Redis::Distributed::CannotDistribute do r.mapped_mget("foo", "bar") end end def test_mset assert_raise Redis::Distributed::CannotDistribute do r.mset(:foo, "s1", :bar, "s2") end end def test_mset_mapped assert_raise Redis::Distributed::CannotDistribute do r.mapped_mset(:foo => "s1", :bar => "s2") end end def test_msetnx assert_raise Redis::Distributed::CannotDistribute do r.set("foo", "s1") r.msetnx(:foo, "s2", :bar, "s3") end end def test_msetnx_mapped assert_raise Redis::Distributed::CannotDistribute do r.set("foo", "s1") r.mapped_msetnx(:foo => "s2", :bar => "s3") end end def test_bitop target_version "2.5.10" do assert_raise Redis::Distributed::CannotDistribute do r.set("foo", "a") r.set("bar", "b") r.bitop(:and, "foo&bar", "foo", "bar") end end end end redis-3.2.2/test/commands_on_sorted_sets_test.rb0000644000004100000410000001050112637240307022133 0ustar www-datawww-data# encoding: UTF-8 require File.expand_path("helper", File.dirname(__FILE__)) require "lint/sorted_sets" class TestCommandsOnSortedSets < Test::Unit::TestCase include Helper::Client include Lint::SortedSets def test_zrangebylex target_version "2.8.9" do r.zadd "foo", 0, "aaren" r.zadd "foo", 0, "abagael" r.zadd "foo", 0, "abby" r.zadd "foo", 0, "abbygail" assert_equal ["aaren", "abagael", "abby", "abbygail"], r.zrangebylex("foo", "[a", "[a\xff") assert_equal ["aaren", "abagael"], r.zrangebylex("foo", "[a", "[a\xff", :limit => [0, 2]) assert_equal ["abby", "abbygail"], r.zrangebylex("foo", "(abb", "(abb\xff") assert_equal ["abbygail"], r.zrangebylex("foo", "(abby", "(abby\xff") end end def test_zrevrangebylex target_version "2.9.9" do r.zadd "foo", 0, "aaren" r.zadd "foo", 0, "abagael" r.zadd "foo", 0, "abby" r.zadd "foo", 0, "abbygail" assert_equal ["abbygail", "abby", "abagael", "aaren"], r.zrevrangebylex("foo", "[a\xff", "[a") assert_equal ["abbygail", "abby"], r.zrevrangebylex("foo", "[a\xff", "[a", :limit => [0, 2]) assert_equal ["abbygail", "abby"], r.zrevrangebylex("foo", "(abb\xff", "(abb") assert_equal ["abbygail"], r.zrevrangebylex("foo", "(abby\xff", "(abby") end end def test_zcount r.zadd "foo", 1, "s1" r.zadd "foo", 2, "s2" r.zadd "foo", 3, "s3" assert_equal 2, r.zcount("foo", 2, 3) end def test_zunionstore r.zadd "foo", 1, "s1" r.zadd "bar", 2, "s2" r.zadd "foo", 3, "s3" r.zadd "bar", 4, "s4" assert_equal 4, r.zunionstore("foobar", ["foo", "bar"]) assert_equal ["s1", "s2", "s3", "s4"], r.zrange("foobar", 0, -1) end def test_zunionstore_with_weights r.zadd "foo", 1, "s1" r.zadd "foo", 3, "s3" r.zadd "bar", 20, "s2" r.zadd "bar", 40, "s4" assert_equal 4, r.zunionstore("foobar", ["foo", "bar"]) assert_equal ["s1", "s3", "s2", "s4"], r.zrange("foobar", 0, -1) assert_equal 4, r.zunionstore("foobar", ["foo", "bar"], :weights => [10, 1]) assert_equal ["s1", "s2", "s3", "s4"], r.zrange("foobar", 0, -1) end def test_zunionstore_with_aggregate r.zadd "foo", 1, "s1" r.zadd "foo", 2, "s2" r.zadd "bar", 4, "s2" r.zadd "bar", 3, "s3" assert_equal 3, r.zunionstore("foobar", ["foo", "bar"]) assert_equal ["s1", "s3", "s2"], r.zrange("foobar", 0, -1) assert_equal 3, r.zunionstore("foobar", ["foo", "bar"], :aggregate => :min) assert_equal ["s1", "s2", "s3"], r.zrange("foobar", 0, -1) assert_equal 3, r.zunionstore("foobar", ["foo", "bar"], :aggregate => :max) assert_equal ["s1", "s3", "s2"], r.zrange("foobar", 0, -1) end def test_zinterstore r.zadd "foo", 1, "s1" r.zadd "bar", 2, "s1" r.zadd "foo", 3, "s3" r.zadd "bar", 4, "s4" assert_equal 1, r.zinterstore("foobar", ["foo", "bar"]) assert_equal ["s1"], r.zrange("foobar", 0, -1) end def test_zinterstore_with_weights r.zadd "foo", 1, "s1" r.zadd "foo", 2, "s2" r.zadd "foo", 3, "s3" r.zadd "bar", 20, "s2" r.zadd "bar", 30, "s3" r.zadd "bar", 40, "s4" assert_equal 2, r.zinterstore("foobar", ["foo", "bar"]) assert_equal ["s2", "s3"], r.zrange("foobar", 0, -1) assert_equal 2, r.zinterstore("foobar", ["foo", "bar"], :weights => [10, 1]) assert_equal ["s2", "s3"], r.zrange("foobar", 0, -1) assert_equal 40.0, r.zscore("foobar", "s2") assert_equal 60.0, r.zscore("foobar", "s3") end def test_zinterstore_with_aggregate r.zadd "foo", 1, "s1" r.zadd "foo", 2, "s2" r.zadd "foo", 3, "s3" r.zadd "bar", 20, "s2" r.zadd "bar", 30, "s3" r.zadd "bar", 40, "s4" assert_equal 2, r.zinterstore("foobar", ["foo", "bar"]) assert_equal ["s2", "s3"], r.zrange("foobar", 0, -1) assert_equal 22.0, r.zscore("foobar", "s2") assert_equal 33.0, r.zscore("foobar", "s3") assert_equal 2, r.zinterstore("foobar", ["foo", "bar"], :aggregate => :min) assert_equal ["s2", "s3"], r.zrange("foobar", 0, -1) assert_equal 2.0, r.zscore("foobar", "s2") assert_equal 3.0, r.zscore("foobar", "s3") assert_equal 2, r.zinterstore("foobar", ["foo", "bar"], :aggregate => :max) assert_equal ["s2", "s3"], r.zrange("foobar", 0, -1) assert_equal 20.0, r.zscore("foobar", "s2") assert_equal 30.0, r.zscore("foobar", "s3") end end redis-3.2.2/test/scanning_test.rb0000644000004100000410000002321112637240307017022 0ustar www-datawww-data# encoding: UTF-8 require File.expand_path("helper", File.dirname(__FILE__)) unless defined?(Enumerator) Enumerator = Enumerable::Enumerator end class TestScanning < Test::Unit::TestCase include Helper::Client def test_scan_basic target_version "2.7.105" do r.debug :populate, 1000 cursor = 0 all_keys = [] loop { cursor, keys = r.scan cursor all_keys += keys break if cursor == "0" } assert_equal 1000, all_keys.uniq.size end end def test_scan_count target_version "2.7.105" do r.debug :populate, 1000 cursor = 0 all_keys = [] loop { cursor, keys = r.scan cursor, :count => 5 all_keys += keys break if cursor == "0" } assert_equal 1000, all_keys.uniq.size end end def test_scan_match target_version "2.7.105" do r.debug :populate, 1000 cursor = 0 all_keys = [] loop { cursor, keys = r.scan cursor, :match => "key:1??" all_keys += keys break if cursor == "0" } assert_equal 100, all_keys.uniq.size end end def test_scan_each_enumerator target_version "2.7.105" do r.debug :populate, 1000 scan_enumerator = r.scan_each assert_equal true, scan_enumerator.is_a?(::Enumerator) keys_from_scan = scan_enumerator.to_a.uniq all_keys = r.keys "*" assert all_keys.sort == keys_from_scan.sort end end def test_scan_each_enumerator_match target_version "2.7.105" do r.debug :populate, 1000 keys_from_scan = r.scan_each(:match => "key:1??").to_a.uniq all_keys = r.keys "key:1??" assert all_keys.sort == keys_from_scan.sort end end def test_scan_each_block target_version "2.7.105" do r.debug :populate, 100 keys_from_scan = [] r.scan_each {|key| keys_from_scan << key } all_keys = r.keys "*" assert all_keys.sort == keys_from_scan.uniq.sort end end def test_scan_each_block_match target_version "2.7.105" do r.debug :populate, 100 keys_from_scan = [] r.scan_each(:match => "key:1?") {|key| keys_from_scan << key } all_keys = r.keys "key:1?" assert all_keys.sort == keys_from_scan.uniq.sort end end def test_sscan_with_encoding target_version "2.7.105" do [:intset, :hashtable].each do |enc| r.del "set" prefix = "" prefix = "ele:" if enc == :hashtable elements = [] 100.times { |j| elements << "#{prefix}#{j}" } r.sadd "set", elements assert_equal enc.to_s, r.object("encoding", "set") cursor = 0 all_keys = [] loop { cursor, keys = r.sscan "set", cursor all_keys += keys break if cursor == "0" } assert_equal 100, all_keys.uniq.size end end end def test_sscan_each_enumerator target_version "2.7.105" do elements = [] 100.times { |j| elements << "ele:#{j}" } r.sadd "set", elements scan_enumerator = r.sscan_each("set") assert_equal true, scan_enumerator.is_a?(::Enumerator) keys_from_scan = scan_enumerator.to_a.uniq all_keys = r.smembers("set") assert all_keys.sort == keys_from_scan.sort end end def test_sscan_each_enumerator_match target_version "2.7.105" do elements = [] 100.times { |j| elements << "ele:#{j}" } r.sadd "set", elements keys_from_scan = r.sscan_each("set", :match => "ele:1?").to_a.uniq all_keys = r.smembers("set").grep(/^ele:1.$/) assert all_keys.sort == keys_from_scan.sort end end def test_sscan_each_enumerator_block target_version "2.7.105" do elements = [] 100.times { |j| elements << "ele:#{j}" } r.sadd "set", elements keys_from_scan = [] r.sscan_each("set") do |key| keys_from_scan << key end all_keys = r.smembers("set") assert all_keys.sort == keys_from_scan.uniq.sort end end def test_sscan_each_enumerator_block_match target_version "2.7.105" do elements = [] 100.times { |j| elements << "ele:#{j}" } r.sadd "set", elements keys_from_scan = [] r.sscan_each("set", :match => "ele:1?") do |key| keys_from_scan << key end all_keys = r.smembers("set").grep(/^ele:1.$/) assert all_keys.sort == keys_from_scan.uniq.sort end end def test_hscan_with_encoding target_version "2.7.105" do [:ziplist, :hashtable].each do |enc| r.del "set" count = 1000 count = 30 if enc == :ziplist elements = [] count.times { |j| elements << "key:#{j}" << j.to_s } r.hmset "hash", *elements assert_equal enc.to_s, r.object("encoding", "hash") cursor = 0 all_key_values = [] loop { cursor, key_values = r.hscan "hash", cursor all_key_values.concat key_values break if cursor == "0" } keys2 = [] all_key_values.each do |k, v| assert_equal "key:#{v}", k keys2 << k end assert_equal count, keys2.uniq.size end end end def test_hscan_each_enumerator target_version "2.7.105" do count = 1000 elements = [] count.times { |j| elements << "key:#{j}" << j.to_s } r.hmset "hash", *elements scan_enumerator = r.hscan_each("hash") assert_equal true, scan_enumerator.is_a?(::Enumerator) keys_from_scan = scan_enumerator.to_a.uniq all_keys = r.hgetall("hash").to_a assert all_keys.sort == keys_from_scan.sort end end def test_hscan_each_enumerator_match target_version "2.7.105" do count = 100 elements = [] count.times { |j| elements << "key:#{j}" << j.to_s } r.hmset "hash", *elements keys_from_scan = r.hscan_each("hash", :match => "key:1?").to_a.uniq all_keys = r.hgetall("hash").to_a.select{|k,v| k =~ /^key:1.$/} assert all_keys.sort == keys_from_scan.sort end end def test_hscan_each_block target_version "2.7.105" do count = 1000 elements = [] count.times { |j| elements << "key:#{j}" << j.to_s } r.hmset "hash", *elements keys_from_scan = [] r.hscan_each("hash") do |field, value| keys_from_scan << [field, value] end all_keys = r.hgetall("hash").to_a assert all_keys.sort == keys_from_scan.uniq.sort end end def test_hscan_each_block_match target_version "2.7.105" do count = 1000 elements = [] count.times { |j| elements << "key:#{j}" << j.to_s } r.hmset "hash", *elements keys_from_scan = [] r.hscan_each("hash", :match => "key:1?") do |field, value| keys_from_scan << [field, value] end all_keys = r.hgetall("hash").to_a.select{|k,v| k =~ /^key:1.$/} assert all_keys.sort == keys_from_scan.uniq.sort end end def test_zscan_with_encoding target_version "2.7.105" do [:ziplist, :skiplist].each do |enc| r.del "zset" count = 1000 count = 30 if enc == :ziplist elements = [] count.times { |j| elements << j << "key:#{j}" } r.zadd "zset", elements assert_equal enc.to_s, r.object("encoding", "zset") cursor = 0 all_key_scores = [] loop { cursor, key_scores = r.zscan "zset", cursor all_key_scores.concat key_scores break if cursor == "0" } keys2 = [] all_key_scores.each do |k, v| assert_equal true, v.is_a?(Float) assert_equal "key:#{Integer(v)}", k keys2 << k end assert_equal count, keys2.uniq.size end end end def test_zscan_each_enumerator target_version "2.7.105" do count = 1000 elements = [] count.times { |j| elements << j << "key:#{j}" } r.zadd "zset", elements scan_enumerator = r.zscan_each "zset" assert_equal true, scan_enumerator.is_a?(::Enumerator) scores_from_scan = scan_enumerator.to_a.uniq member_scores = r.zrange("zset", 0, -1, :with_scores => true) assert member_scores.sort == scores_from_scan.sort end end def test_zscan_each_enumerator_match target_version "2.7.105" do count = 1000 elements = [] count.times { |j| elements << j << "key:#{j}" } r.zadd "zset", elements scores_from_scan = r.zscan_each("zset", :match => "key:1??").to_a.uniq member_scores = r.zrange("zset", 0, -1, :with_scores => true) filtered_members = member_scores.select{|k,s| k =~ /^key:1..$/} assert filtered_members.sort == scores_from_scan.sort end end def test_zscan_each_block target_version "2.7.105" do count = 1000 elements = [] count.times { |j| elements << j << "key:#{j}" } r.zadd "zset", elements scores_from_scan = [] r.zscan_each("zset") do |member, score| scores_from_scan << [member, score] end member_scores = r.zrange("zset", 0, -1, :with_scores => true) assert member_scores.sort == scores_from_scan.sort end end def test_zscan_each_block_match target_version "2.7.105" do count = 1000 elements = [] count.times { |j| elements << j << "key:#{j}" } r.zadd "zset", elements scores_from_scan = [] r.zscan_each("zset", :match => "key:1??") do |member, score| scores_from_scan << [member, score] end member_scores = r.zrange("zset", 0, -1, :with_scores => true) filtered_members = member_scores.select{|k,s| k =~ /^key:1..$/} assert filtered_members.sort == scores_from_scan.sort end end end redis-3.2.2/test/distributed_test.rb0000644000004100000410000000277512637240307017560 0ustar www-datawww-data# encoding: UTF-8 require File.expand_path("helper", File.dirname(__FILE__)) class TestDistributed < Test::Unit::TestCase include Helper::Distributed def test_handle_multiple_servers @r = Redis::Distributed.new ["redis://localhost:#{PORT}/15", *NODES] 100.times do |idx| @r.set(idx.to_s, "foo#{idx}") end 100.times do |idx| assert_equal "foo#{idx}", @r.get(idx.to_s) end assert_equal "0", @r.keys("*").sort.first assert_equal "string", @r.type("1") end def test_add_nodes logger = Logger.new("/dev/null") @r = Redis::Distributed.new NODES, :logger => logger, :timeout => 10 assert_equal "127.0.0.1", @r.nodes[0].client.host assert_equal PORT, @r.nodes[0].client.port assert_equal 15, @r.nodes[0].client.db assert_equal 10, @r.nodes[0].client.timeout assert_equal logger, @r.nodes[0].client.logger @r.add_node("redis://127.0.0.1:6380/14") assert_equal "127.0.0.1", @r.nodes[1].client.host assert_equal 6380, @r.nodes[1].client.port assert_equal 14, @r.nodes[1].client.db assert_equal 10, @r.nodes[1].client.timeout assert_equal logger, @r.nodes[1].client.logger end def test_pipelining_commands_cannot_be_distributed assert_raise Redis::Distributed::CannotDistribute do r.pipelined do r.lpush "foo", "s1" r.lpush "foo", "s2" end end end def test_unknown_commands_does_not_work_by_default assert_raise NoMethodError do r.not_yet_implemented_command end end end redis-3.2.2/test/distributed_transactions_test.rb0000644000004100000410000000115512637240307022337 0ustar www-datawww-data# encoding: UTF-8 require File.expand_path("helper", File.dirname(__FILE__)) class TestDistributedTransactions < Test::Unit::TestCase include Helper::Distributed def test_multi_discard @foo = nil assert_raise Redis::Distributed::CannotDistribute do r.multi { @foo = 1 } end assert_equal nil, @foo assert_raise Redis::Distributed::CannotDistribute do r.discard end end def test_watch_unwatch assert_raise Redis::Distributed::CannotDistribute do r.watch("foo") end assert_raise Redis::Distributed::CannotDistribute do r.unwatch end end end redis-3.2.2/test/bitpos_test.rb0000644000004100000410000000304012637240307016520 0ustar www-datawww-data# encoding: UTF-8 require File.expand_path("helper", File.dirname(__FILE__)) unless defined?(Enumerator) Enumerator = Enumerable::Enumerator end class TestBitpos < Test::Unit::TestCase include Helper::Client def test_bitpos_empty_zero target_version "2.9.11" do r.del "foo" assert_equal 0, r.bitpos("foo", 0) end end def test_bitpos_empty_one target_version "2.9.11" do r.del "foo" assert_equal -1, r.bitpos("foo", 1) end end def test_bitpos_zero target_version "2.9.11" do r.set "foo", "\xff\xf0\x00" assert_equal 12, r.bitpos("foo", 0) end end def test_bitpos_one target_version "2.9.11" do r.set "foo", "\x00\x0f\x00" assert_equal 12, r.bitpos("foo", 1) end end def test_bitpos_zero_end_is_given target_version "2.9.11" do r.set "foo", "\xff\xff\xff" assert_equal 24, r.bitpos("foo", 0) assert_equal 24, r.bitpos("foo", 0, 0) assert_equal -1, r.bitpos("foo", 0, 0, -1) end end def test_bitpos_one_intervals target_version "2.9.11" do r.set "foo", "\x00\xff\x00" assert_equal 8, r.bitpos("foo", 1, 0, -1) assert_equal 8, r.bitpos("foo", 1, 1, -1) assert_equal -1, r.bitpos("foo", 1, 2, -1) assert_equal -1, r.bitpos("foo", 1, 2, 200) assert_equal 8, r.bitpos("foo", 1, 1, 1) end end def test_bitpos_raise_exception_if_stop_not_start target_version "2.9.11" do assert_raises(ArgumentError) do r.bitpos("foo", 0, nil, 2) end end end end redis-3.2.2/test/encoding_test.rb0000644000004100000410000000060112637240307017006 0ustar www-datawww-data# encoding: UTF-8 require File.expand_path("helper", File.dirname(__FILE__)) class TestEncoding < Test::Unit::TestCase include Helper::Client def test_returns_properly_encoded_strings if defined?(Encoding) with_external_encoding("UTF-8") do r.set "foo", "שלום" assert_equal "Shalom שלום", "Shalom " + r.get("foo") end end end end redis-3.2.2/.gitignore0000644000004100000410000000024312637240307014647 0ustar www-datawww-data*.rdb *.swp Gemfile.lock *.gem /tmp/ /.idea /.yardoc /coverage/* /doc/ /examples/sentinel/sentinel.conf /nohup.out /pkg/* /rdsrv /redis/* /test/db /test/test.conf redis-3.2.2/redis.gemspec0000644000004100000410000000212112637240307015327 0ustar www-datawww-data# -*- encoding: utf-8 -*- $:.unshift File.expand_path("../lib", __FILE__) require "redis/version" Gem::Specification.new do |s| s.name = "redis" s.version = Redis::VERSION s.homepage = "https://github.com/redis/redis-rb" s.summary = "A Ruby client library for Redis" s.description = <<-EOS A Ruby client that tries to match Redis' API one-to-one, while still providing an idiomatic interface. It features thread-safety, client-side sharding, pipelining, and an obsession for performance. EOS s.license = "MIT" s.authors = [ "Ezra Zygmuntowicz", "Taylor Weibley", "Matthew Clark", "Brian McKinney", "Salvatore Sanfilippo", "Luca Guidi", "Michel Martens", "Damian Janowski", "Pieter Noordhuis" ] s.email = ["redis-db@googlegroups.com"] s.files = `git ls-files`.split("\n") s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n") s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) } s.add_development_dependency("rake") s.add_development_dependency("test-unit") end redis-3.2.2/.yardopts0000644000004100000410000000010412637240307014521 0ustar www-datawww-data--exclude redis/connection --exclude redis/compat --markup markdown redis-3.2.2/LICENSE0000644000004100000410000000204412637240307013665 0ustar www-datawww-dataCopyright (c) 2009 Ezra Zygmuntowicz Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.redis-3.2.2/.travis/0000755000004100000410000000000012637240307014246 5ustar www-datawww-dataredis-3.2.2/.travis/Gemfile0000644000004100000410000000024112637240307015536 0ustar www-datawww-datasource "https://rubygems.org" gemspec :path => "../" case ENV["conn"] when "hiredis" gem "hiredis" when "synchrony" gem "hiredis" gem "em-synchrony" end redis-3.2.2/CHANGELOG.md0000644000004100000410000002434612637240307014502 0ustar www-datawww-data# 4.x (unreleased) ## Planned breaking changes: * `Redis#client` will no longer expose the underlying `Redis::Client`; it has not yet been determined how 4.0 will expose the underlying functionality, but we will make every attempt to provide a final minor release of 3.x that provides the new interfaces in order to facilitate a smooth transition. * Ruby 1.8.7 (and the 1.8 modes of JRuby and Rubinius) will no longer be supported; 1.8.x entered end-of-life in June of 2012 and stopped receiving security updates in June of 2013; continuing to support it would prevent the use of newer features of Ruby. # 3.2.2 * Added support for `ZADD` options `NX`, `XX`, `CH`, `INCR`. See #547. * Added support for sentinel commands. See #556. * New `:id` option allows you to identify the client against Redis. See #510. * `Redis::Distributed` will raise when adding two nodes with the same ID. See #354. # 3.2.1 * Added support for `PUBSUB` command. * More low-level socket errors are now raised as `CannotConnectError`. * Added `:connect_timeout` option. * Added support for `:limit` option for `ZREVRANGEBYLEX`. * Fixed an issue where connections become inconsistent when using Ruby's Timeout module outside of the client (see #501, #502). * Added `Redis#disconnect!` as a public-API way of disconnecting the client (without needing to use `QUIT`). See #506. * Fixed Sentinel support with Hiredis. * Fixed Sentinel support when using authentication and databases. * Improved resilience when trying to contact sentinels. # 3.2.0 * Redis Sentinel support. # 3.1.0 * Added debug log sanitization (#428). * Added support for HyperLogLog commands (Redis 2.8.9, #432). * Added support for `BITPOS` command (Redis 2.9.11, #412). * The client will now automatically reconnect after a fork (#414). * If you want to disable the fork-safety check and prefer to share the connection across child processes, you can now pass the `inherit_socket` option (#409). * If you want the client to attempt to reconnect more than once, you can now pass the `reconnect_attempts` option (#347) # 3.0.7 * Added method `Redis#dup` to duplicate a Redis connection. * IPv6 support. # 3.0.6 * Added support for `SCAN` and variants. # 3.0.5 * Fix calling #select from a pipeline (#309). * Added method `Redis#connected?`. * Added support for `MIGRATE` (Redis 2.6). * Support extended SET command (#343, thanks to @benubois). # 3.0.4 * Ensure #watch without a block returns "OK" (#332). * Make futures identifiable (#330). * Fix an issue preventing STORE in a SORT with multiple GETs (#328). # 3.0.3 * Blocking list commands (`BLPOP`, `BRPOP`, `BRPOPLPUSH`) use a socket timeout equal to the sum of the command's timeout and the Redis client's timeout, instead of disabling socket timeout altogether. * Ruby 2.0 compatibility. * Added support for `DUMP` and `RESTORE` (Redis 2.6). * Added support for `BITCOUNT` and `BITOP` (Redis 2.6). * Call `#to_s` on value argument for `SET`, `SETEX`, `PSETEX`, `GETSET`, `SETNX`, and `SETRANGE`. # 3.0.2 * Unescape CGI escaped password in URL. * Fix test to check availability of `UNIXSocket`. * Fix handling of score = +/- infinity for sorted set commands. * Replace array splats with concatenation where possible. * Raise if `EXEC` returns an error. * Passing a nil value in options hash no longer overwrites the default. * Allow string keys in options hash passed to `Redis.new` or `Redis.connect`. * Fix uncaught error triggering unrelated error (synchrony driver). See f7ffd5f1a628029691084de69e5b46699bb8b96d and #248. # 3.0.1 * Fix reconnect logic not kicking in on a write error. See 427dbd52928af452f35aa0a57b621bee56cdcb18 and #238. # 3.0.0 ### Upgrading from 2.x to 3.0 The following items are the most important changes to review when upgrading from redis-rb 2.x. A full list of changes can be found below. * The methods for the following commands have changed the arguments they take, their return value, or both. * `BLPOP`, `BRPOP`, `BRPOPLPUSH` * `SORT` * `MSETNX` * `ZRANGE`, `ZREVRANGE`, `ZRANGEBYSCORE`, `ZREVRANGEBYSCORE` * `ZINCRBY`, `ZSCORE` * The return value from `#pipelined` and `#multi` no longer contains unprocessed replies, but the same replies that would be returned if the command had not been executed in these blocks. * The client raises custom errors on connection errors, instead of `RuntimeError` and errors in the `Errno` family. ### Changes * Added support for scripting commands (Redis 2.6). Scripts can be executed using `#eval` and `#evalsha`. Both can commands can either take two arrays to specify `KEYS` and `ARGV`, or take a hash containing `:keys` and `:argv` to specify `KEYS` and `ARGV`. ```ruby redis.eval("return ARGV[1] * ARGV[2]", :argv => [2, 3]) # => 6 ``` Subcommands of the `SCRIPT` command can be executed via the `#script` method. For example: ```ruby redis.script(:load, "return ARGV[1] * ARGV[2]") # => "58db5d365a1922f32e7aa717722141ea9c2b0cf3" redis.script(:exists, "58db5d365a1922f32e7aa717722141ea9c2b0cf3") # => true redis.script(:flush) # => "OK" ``` * The repository now lives at [https://github.com/redis/redis-rb](https://github.com/redis/redis-rb). Thanks, Ezra! * Added support for `PEXPIRE`, `PEXPIREAT`, `PTTL`, `PSETEX`, `INCRYBYFLOAT`, `HINCRYBYFLOAT` and `TIME` (Redis 2.6). * `Redis.current` is now thread unsafe, because the client itself is thread safe. In the future you'll be able to do something like: ```ruby Redis.current = Redis::Pool.connect ``` This makes `Redis.current` actually usable in multi-threaded environments, while not affecting those running a single thread. * Change API for `BLPOP`, `BRPOP` and `BRPOPLPUSH`. Both `BLPOP` and `BRPOP` now take a single argument equal to a string key, or an array with string keys, followed by an optional hash with a `:timeout` key. When not specified, the timeout defaults to `0` to not time out. ```ruby redis.blpop(["list1", "list2"], :timeout => 1.0) ``` `BRPOPLPUSH` also takes an optional hash with a `:timeout` key as last argument for consistency. When not specified, the timeout defaults to `0` to not time out. ```ruby redis.brpoplpush("some_list", "another_list", :timeout => 1.0) ``` * When `SORT` is passed multiple key patterns to get via the `:get` option, it now returns an array per result element, holding all `GET` substitutions. * The `MSETNX` command now returns a boolean. * The `ZRANGE`, `ZREVRANGE`, `ZRANGEBYSCORE` and `ZREVRANGEBYSCORE` commands now return an array containing `[String, Float]` pairs when `:with_scores => true` is passed. For example: ```ruby redis.zrange("zset", 0, -1, :with_scores => true) # => [["foo", 1.0], ["bar", 2.0]] ``` * The `ZINCRBY` and `ZSCORE` commands now return a `Float` score instead of a string holding a representation of the score. * The client now raises custom exceptions where it makes sense. If by any chance you were rescuing low-level exceptions (`Errno::*`), you should now rescue as follows: Errno::ECONNRESET -> Redis::ConnectionError Errno::EPIPE -> Redis::ConnectionError Errno::ECONNABORTED -> Redis::ConnectionError Errno::EBADF -> Redis::ConnectionError Errno::EINVAL -> Redis::ConnectionError Errno::EAGAIN -> Redis::TimeoutError Errno::ECONNREFUSED -> Redis::CannotConnectError * Always raise exceptions originating from erroneous command invocation inside pipelines and MULTI/EXEC blocks. The old behavior (swallowing exceptions) could cause application bugs to go unnoticed. * Implement futures for assigning values inside pipelines and MULTI/EXEC blocks. Futures are assigned their value after the pipeline or MULTI/EXEC block has executed. ```ruby $redis.pipelined do @future = $redis.get "key" end puts @future.value ``` * Ruby 1.8.6 is officially not supported. * Support `ZCOUNT` in `Redis::Distributed` (Michael Dungan). * Pipelined commands now return the same replies as when called outside a pipeline. In the past, pipelined replies were returned without post-processing. * Support `SLOWLOG` command (Michael Bernstein). * Calling `SHUTDOWN` effectively disconnects the client (Stefan Kaes). * Basic support for mapping commands so that they can be renamed on the server. * Connecting using a URL now checks that a host is given. It's just a small sanity check, cf. #126 * Support variadic commands introduced in Redis 2.4. # 2.2.2 * Added method `Redis::Distributed#hsetnx`. # 2.2.1 * Internal API: Client#call and family are now called with a single array argument, since splatting a large number of arguments (100K+) results in a stack overflow on 1.9.2. * The `INFO` command can optionally take a subcommand. When the subcommand is `COMMANDSTATS`, the client will properly format the returned statistics per command. Subcommands for `INFO` are available since Redis v2.3.0 (unstable). * Change `IO#syswrite` back to the buffered `IO#write` since some Rubies do short writes for large (1MB+) buffers and some don't (see issue #108). # 2.2.0 * Added method `Redis#without_reconnect` that ensures the client will not try to reconnect when running the code inside the specified block. * Thread-safe by default. Thread safety can be explicitly disabled by passing `:thread_safe => false` as argument. * Commands called inside a MULTI/EXEC no longer raise error replies, since a successful EXEC means the commands inside the block were executed. * MULTI/EXEC blocks are pipelined. * Don't disconnect on error replies. * Use `IO#syswrite` instead of `IO#write` because write buffering is not necessary. * Connect to a unix socket by passing the `:path` option as argument. * The timeout value is coerced into a float, allowing sub-second timeouts. * Accept both `:with_scores` _and_ `:withscores` as argument to sorted set commands. * Use [hiredis](https://github.com/pietern/hiredis-rb) (v0.3 or higher) by requiring "redis/connection/hiredis". * Use [em-synchrony](https://github.com/igrigorik/em-synchrony) by requiring "redis/connection/synchrony". # 2.1.1 See commit log. redis-3.2.2/README.md0000644000004100000410000002233512637240307014144 0ustar www-datawww-data# redis-rb [![Build Status][travis-image]][travis-link] [![Inline docs][inchpages-image]][inchpages-link] [travis-image]: https://secure.travis-ci.org/redis/redis-rb.png?branch=master [travis-link]: http://travis-ci.org/redis/redis-rb [travis-home]: http://travis-ci.org/ [inchpages-image]: http://inch-ci.org/github/redis/redis-rb.png [inchpages-link]: http://inch-ci.org/github/redis/redis-rb A Ruby client library for [Redis][redis-home]. [redis-home]: http://redis.io A Ruby client that tries to match Redis' API one-to-one, while still providing an idiomatic interface. It features thread-safety, client-side sharding, pipelining, and an obsession for performance. ## Upgrading from 2.x to 3.0 Please refer to the [CHANGELOG][changelog-3.0.0] for a summary of the most important changes, as well as a full list of changes. [changelog-3.0.0]: https://github.com/redis/redis-rb/blob/master/CHANGELOG.md#300 ## Getting started To install **redis-rb**, run the following command: ``` gem install redis ``` Or if you are using **bundler**, add ``` gem 'redis', '~>3.2' ``` to your `Gemfile`, and run `bundle install` As of version 2.0 this client only targets Redis version 2.0 and higher. You can use an older version of this client if you need to interface with a Redis instance older than 2.0, but this is no longer supported. You can connect to Redis by instantiating the `Redis` class: ```ruby require "redis" redis = Redis.new ``` This assumes Redis was started with a default configuration, and is listening on `localhost`, port 6379. If you need to connect to a remote server or a different port, try: ```ruby redis = Redis.new(:host => "10.0.1.1", :port => 6380, :db => 15) ``` You can also specify connection options as a [`redis://` URL][redis-url]: ```ruby redis = Redis.new(:url => "redis://:p4ssw0rd@10.0.1.1:6380/15") ``` [redis-url]: http://www.iana.org/assignments/uri-schemes/prov/redis By default, the client will try to read the `REDIS_URL` environment variable and use that as URL to connect to. The above statement is therefore equivalent to setting this environment variable and calling `Redis.new` without arguments. To connect to Redis listening on a Unix socket, try: ```ruby redis = Redis.new(:path => "/tmp/redis.sock") ``` To connect to a password protected Redis instance, use: ```ruby redis = Redis.new(:password => "mysecret") ``` The Redis class exports methods that are named identical to the commands they execute. The arguments these methods accept are often identical to the arguments specified on the [Redis website][redis-commands]. For instance, the `SET` and `GET` commands can be called like this: [redis-commands]: http://redis.io/commands ```ruby redis.set("mykey", "hello world") # => "OK" redis.get("mykey") # => "hello world" ``` All commands, their arguments and return values are documented, and available on [rdoc.info][rdoc]. [rdoc]: http://rdoc.info/github/redis/redis-rb/ ## Sentinel support The client is able to perform automatic failovers by using [Redis Sentinel](http://redis.io/topics/sentinel). Make sure to run Redis 2.8+ if you want to use this feature. To connect using Sentinel, use: ```ruby SENTINELS = [{:host => "127.0.0.1", :port => 26380}, {:host => "127.0.0.1", :port => 26381}] redis = Redis.new(:url => "redis://mymaster", :sentinels => SENTINELS, :role => :master) ``` * The master name identifies a group of Redis instances composed of a master and one or more slaves (`mymaster` in the example). * It is possible to optionally provide a role. The allowed roles are `master` and `slave`. When the role is `slave`, the client will try to connect to a random slave of the specified master. If a role is not specified, the client will connect to the master. * When using the Sentinel support you need to specify a list of sentinels to connect to. The list does not need to enumerate all your Sentinel instances, but a few so that if one is down the client will try the next one. The client is able to remember the last Sentinel that was able to reply correctly and will use it for the next requests. ## Storing objects Redis only stores strings as values. If you want to store an object, you can use a serialization mechanism such as JSON: ```ruby require "json" redis.set "foo", [1, 2, 3].to_json # => OK JSON.parse(redis.get("foo")) # => [1, 2, 3] ``` ## Pipelining When multiple commands are executed sequentially, but are not dependent, the calls can be *pipelined*. This means that the client doesn't wait for reply of the first command before sending the next command. The advantage is that multiple commands are sent at once, resulting in faster overall execution. The client can be instructed to pipeline commands by using the `#pipelined` method. After the block is executed, the client sends all commands to Redis and gathers their replies. These replies are returned by the `#pipelined` method. ```ruby redis.pipelined do redis.set "foo", "bar" redis.incr "baz" end # => ["OK", 1] ``` ### Executing commands atomically You can use `MULTI/EXEC` to run a number of commands in an atomic fashion. This is similar to executing a pipeline, but the commands are preceded by a call to `MULTI`, and followed by a call to `EXEC`. Like the regular pipeline, the replies to the commands are returned by the `#multi` method. ```ruby redis.multi do redis.set "foo", "bar" redis.incr "baz" end # => ["OK", 1] ``` ### Futures Replies to commands in a pipeline can be accessed via the *futures* they emit (since redis-rb 3.0). All calls inside a pipeline block return a `Future` object, which responds to the `#value` method. When the pipeline has successfully executed, all futures are assigned their respective replies and can be used. ```ruby redis.pipelined do @set = redis.set "foo", "bar" @incr = redis.incr "baz" end @set.value # => "OK" @incr.value # => 1 ``` ## Error Handling In general, if something goes wrong you'll get an exception. For example, if it can't connect to the server a `Redis::CannotConnectError` error will be raised. ```ruby begin redis.ping rescue Exception => e e.inspect # => # e.message # => Timed out connecting to Redis on 10.0.1.1:6380 end ``` See lib/redis/errors.rb for information about what exceptions are possible. ## Expert-Mode Options - `inherit_socket: true`: disable safety check that prevents a forked child from sharing a socket with its parent; this is potentially useful in order to mitigate connection churn when: - many short-lived forked children of one process need to talk to redis, AND - your own code prevents the parent process from using the redis connection while a child is alive Improper use of `inherit_socket` will result in corrupted and/or incorrect responses. ## Alternate drivers By default, redis-rb uses Ruby's socket library to talk with Redis. To use an alternative connection driver it should be specified as option when instantiating the client object. These instructions are only valid for **redis-rb 3.0**. For instructions on how to use alternate drivers from **redis-rb 2.2**, please refer to an [older README][readme-2.2.2]. [readme-2.2.2]: https://github.com/redis/redis-rb/blob/v2.2.2/README.md ### hiredis The hiredis driver uses the connection facility of hiredis-rb. In turn, hiredis-rb is a binding to the official hiredis client library. It optimizes for speed, at the cost of portability. Because it is a C extension, JRuby is not supported (by default). It is best to use hiredis when you have large replies (for example: `LRANGE`, `SMEMBERS`, `ZRANGE`, etc.) and/or use big pipelines. In your Gemfile, include hiredis: ```ruby gem "redis", "~> 3.0.1" gem "hiredis", "~> 0.4.5" ``` When instantiating the client object, specify hiredis: ```ruby redis = Redis.new(:driver => :hiredis) ``` ### synchrony The synchrony driver adds support for [em-synchrony][em-synchrony]. This makes redis-rb work with EventMachine's asynchronous I/O, while not changing the exposed API. The hiredis gem needs to be available as well, because the synchrony driver uses hiredis for parsing the Redis protocol. [em-synchrony]: https://github.com/igrigorik/em-synchrony In your Gemfile, include em-synchrony and hiredis: ```ruby gem "redis", "~> 3.0.1" gem "hiredis", "~> 0.4.5" gem "em-synchrony" ``` When instantiating the client object, specify synchrony: ```ruby redis = Redis.new(:driver => :synchrony) ``` ## Testing This library is tested using [Travis][travis-home], where it is tested against the following interpreters and drivers: * MRI 1.8.7 (drivers: ruby, hiredis) * MRI 1.9.2 (drivers: ruby, hiredis, synchrony) * MRI 1.9.3 (drivers: ruby, hiredis, synchrony) * MRI 2.0.0 (drivers: ruby, hiredis, synchrony) * JRuby 1.7 (1.8 mode) (drivers: ruby) * JRuby 1.7 (1.9 mode) (drivers: ruby) ## Contributors (ordered chronologically with more than 5 commits, see `git shortlog -sn` for all contributors) * Ezra Zygmuntowicz * Taylor Weibley * Matthew Clark * Brian McKinney * Luca Guidi * Salvatore Sanfilippo * Chris Wanstrath * Damian Janowski * Michel Martens * Nick Quaranto * Pieter Noordhuis * Ilya Grigorik ## Contributing [Fork the project](https://github.com/redis/redis-rb) and send pull requests. You can also ask for help at `#redis-rb` on Freenode.