Commit 1259672f authored by antirez's avatar antirez
Browse files

client libs removed from Redis git

parent 5762b7f0
BENCHMARK_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] <start_index> <end_index> <sleep_msec>
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
require 'fileutils'
class RedisCluster
def initialize(opts={})
opts = {:port => 6379, :host => 'localhost', :basedir => "#{Dir.pwd}/rdsrv" }.merge(opts)
FileUtils.mkdir_p opts[:basedir]
opts[:size].times do |i|
port = opts[:port] + i
FileUtils.mkdir_p "#{opts[:basedir]}/#{port}"
File.open("#{opts[:basedir]}/#{port}.conf", 'w'){|f| f.write(make_config(port, "#{opts[:basedir]}/#{port}", "#{opts[:basedir]}/#{port}.log"))}
system(%Q{#{File.join(File.expand_path(File.dirname(__FILE__)), "../redis/redis-server #{opts[:basedir]}/#{port}.conf &" )}})
end
end
def make_config(port=6379, data=port, logfile='stdout', loglevel='debug')
config = %Q{
timeout 300
save 900 1
save 300 10
save 60 10000
dir #{data}
loglevel #{loglevel}
logfile #{logfile}
databases 16
port #{port}
}
end
end
RedisCluster.new :size => 4
\ No newline at end of file
require 'rubygems'
require 'redis'
r = Redis.new
r.delete('foo')
puts
p'set foo to "bar"'
r['foo'] = 'bar'
puts
p 'value of foo'
p r['foo']
require 'rubygems'
require 'redis'
r = Redis.new
puts
p 'incr'
r.delete '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')
require 'rubygems'
require 'redis'
r = Redis.new
r.delete 'logs'
puts
p "pushing log messages into a LIST"
r.push_tail 'logs', 'some log message'
r.push_tail 'logs', 'another log message'
r.push_tail 'logs', 'yet another log message'
r.push_tail 'logs', 'also another log message'
puts
p 'contents of logs LIST'
p r.list_range('logs', 0, -1)
puts
p 'Trim logs LIST to last 2 elements(easy circular buffer)'
r.list_trim('logs', -2, -1)
p r.list_range('logs', 0, -1)
require 'rubygems'
require 'redis'
r = Redis.new
r.delete 'foo-tags'
r.delete 'bar-tags'
puts
p "create a set of tags on foo-tags"
r.set_add 'foo-tags', 'one'
r.set_add 'foo-tags', 'two'
r.set_add 'foo-tags', 'three'
puts
p "create a set of tags on bar-tags"
r.set_add 'bar-tags', 'three'
r.set_add 'bar-tags', 'four'
r.set_add 'bar-tags', 'five'
puts
p 'foo-tags'
p r.set_members('foo-tags')
puts
p 'bar-tags'
p r.set_members('bar-tags')
puts
p 'intersection of foo-tags and bar-tags'
p r.set_intersect('foo-tags', 'bar-tags')
require 'redis'
require 'hash_ring'
class DistRedis
attr_reader :ring
def initialize(opts={})
hosts = []
db = opts[:db] || nil
timeout = opts[:timeout] || nil
raise Error, "No hosts given" unless opts[:hosts]
opts[:hosts].each do |h|
host, port = h.split(':')
hosts << Redis.new(:host => host, :port => port, :db => db, :timeout => timeout)
end
@ring = HashRing.new hosts
end
def node_for_key(key)
key = $1 if key =~ /\{(.*)?\}/
@ring.get_node(key)
end
def add_server(server)
server, port = server.split(':')
@ring.add_node Redis.new(:host => server, :port => port)
end
def method_missing(sym, *args, &blk)
if redis = node_for_key(args.first.to_s)
redis.send sym, *args, &blk
else
super
end
end
def keys(glob)
@ring.nodes.map do |red|
red.keys(glob)
end
end
def save
on_each_node :save
end
def bgsave
on_each_node :bgsave
end
def quit
on_each_node :quit
end
def flush_all
on_each_node :flush_all
end
alias_method :flushall, :flush_all
def flush_db
on_each_node :flush_db
end
alias_method :flushdb, :flush_db
def delete_cloud!
@ring.nodes.each do |red|
red.keys("*").each do |key|
red.delete key
end
end
end
def on_each_node(command, *args)
@ring.nodes.each do |red|
red.send(command, *args)
end
end
end
if __FILE__ == $0
r = DistRedis.new 'localhost:6379', 'localhost:6380', 'localhost:6381', 'localhost:6382'
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.push_tail 'listor', 'foo1'
r.push_tail 'listor', 'foo2'
r.push_tail 'listor', 'foo3'
r.push_tail 'listor', 'foo4'
r.push_tail 'listor', 'foo5'
p r.pop_tail('listor')
p r.pop_tail('listor')
p r.pop_tail('listor')
p r.pop_tail('listor')
p r.pop_tail('listor')
puts "key distribution:"
r.ring.nodes.each do |red|
p [red.port, red.keys("*")]
end
r.delete_cloud!
p r.keys('*')
end
require 'zlib'
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}:#{i}")
@ring[key] = node
@sorted_keys << key
end
@sorted_keys.sort!
end
def remove_node(node)
@nodes.reject!{|n| n.to_s == node.to_s}
@replicas.times do |i|
key = Zlib.crc32("#{node}:#{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
node, pos = get_node_pos(key)
@sorted_keys[pos..-1].each do |k|
yield @ring[k]
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;
}
}
return upper;
}
EOM
end
rescue Exception => e
# 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
return upper
end
end
end
end
# ring = HashRing.new ['server1', 'server2', 'server3']
# p ring
# #
# p ring.get_node "kjhjkjlkjlkkh"
#
\ No newline at end of file
require "redis"
class Redis
class Pipeline < Redis
BUFFER_SIZE = 50_000
def initialize(redis)
@redis = redis
@commands = []
end
def call_command(command)
@commands << command
end
def execute
@redis.call_command(@commands)
@commands.clear
end
end
end
require 'socket'
require File.join(File.dirname(__FILE__),'pipeline')
begin
if RUBY_VERSION >= '1.9'
require 'timeout'
RedisTimer = Timeout
else
require 'system_timer'
RedisTimer = SystemTimer
end
rescue LoadError
RedisTimer = nil
end
class Redis
OK = "OK".freeze
MINUS = "-".freeze
PLUS = "+".freeze
COLON = ":".freeze
DOLLAR = "$".freeze
ASTERISK = "*".freeze
BULK_COMMANDS = {
"set" => true,
"setnx" => true,
"rpush" => true,
"lpush" => true,
"lset" => true,
"lrem" => true,
"sadd" => true,
"srem" => true,
"sismember" => true,
"echo" => true,
"getset" => true,
"smove" => true
}
BOOLEAN_PROCESSOR = lambda{|r| r == 1 }
REPLY_PROCESSOR = {
"exists" => BOOLEAN_PROCESSOR,
"sismember" => BOOLEAN_PROCESSOR,
"sadd" => BOOLEAN_PROCESSOR,
"srem" => BOOLEAN_PROCESSOR,
"smove" => BOOLEAN_PROCESSOR,
"move" => BOOLEAN_PROCESSOR,
"setnx" => BOOLEAN_PROCESSOR,
"del" => BOOLEAN_PROCESSOR,
"renamenx" => BOOLEAN_PROCESSOR,
"expire" => BOOLEAN_PROCESSOR,
"keys" => lambda{|r| r.split(" ")},
"info" => lambda{|r|
info = {}
r.each_line {|kv|
k,v = kv.split(":",2).map{|x| x.chomp}
info[k.to_sym] = v
}
info
}
}
ALIASES = {
"flush_db" => "flushdb",
"flush_all" => "flushall",
"last_save" => "lastsave",
"key?" => "exists",
"delete" => "del",
"randkey" => "randomkey",
"list_length" => "llen",
"push_tail" => "rpush",
"push_head" => "lpush",
"pop_tail" => "rpop",
"pop_head" => "lpop",
"list_set" => "lset",
"list_range" => "lrange",
"list_trim" => "ltrim",
"list_index" => "lindex",
"list_rm" => "lrem",
"set_add" => "sadd",
"set_delete" => "srem",
"set_count" => "scard",
"set_member?" => "sismember",
"set_members" => "smembers",
"set_intersect" => "sinter",
"set_intersect_store" => "sinterstore",
"set_inter_store" => "sinterstore",
"set_union" => "sunion",
"set_union_store" => "sunionstore",
"set_diff" => "sdiff",
"set_diff_store" => "sdiffstore",
"set_move" => "smove",
"set_unless_exists" => "setnx",
"rename_unless_exists" => "renamenx",
"type?" => "type"
}
DISABLED_COMMANDS = {
"monitor" => true,
"sync" => true
}
def initialize(options = {})
@host = options[:host] || '127.0.0.1'
@port = (options[:port] || 6379).to_i
@db = (options[:db] || 0).to_i
@timeout = (options[:timeout] || 5).to_i
@password = options[:password]
@logger = options[:logger]
@logger.info { self.to_s } if @logger
connect_to_server
end
def to_s
"Redis Client connected to #{server} against DB #{@db}"
end
def server
"#{@host}:#{@port}"
end
def connect_to_server
@sock = connect_to(@host, @port, @timeout == 0 ? nil : @timeout)
call_command(["auth",@password]) if @password
call_command(["select",@db]) unless @db == 0
end
def connect_to(host, port, timeout=nil)
# We support connect() timeout only if system_timer is availabe
# or if we are running against Ruby >= 1.9
# Timeout reading from the socket instead will be supported anyway.
if @timeout != 0 and RedisTimer
begin
sock = TCPSocket.new(host, port)
rescue Timeout::Error
@sock = nil
raise Timeout::Error, "Timeout connecting to the server"
end
else
sock = TCPSocket.new(host, port)
end
sock.setsockopt Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1
# If the timeout is set we set the low level socket options in order
# to make sure a blocking read will return after the specified number
# of seconds. This hack is from memcached ruby client.
if timeout
secs = Integer(timeout)
usecs = Integer((timeout - secs) * 1_000_000)
optval = [secs, usecs].pack("l_2")
sock.setsockopt Socket::SOL_SOCKET, Socket::SO_RCVTIMEO, optval
sock.setsockopt Socket::SOL_SOCKET, Socket::SO_SNDTIMEO, optval
end
sock
end
def method_missing(*argv)
call_command(argv)
end
def call_command(argv)
@logger.debug { argv.inspect } if @logger
# this wrapper to raw_call_command handle reconnection on socket
# error. We try to reconnect just one time, otherwise let the error
# araise.
connect_to_server if !@sock
begin
raw_call_command(argv.dup)
rescue Errno::ECONNRESET, Errno::EPIPE
@sock.close
@sock = nil
connect_to_server
raw_call_command(argv.dup)
end
end
def raw_call_command(argvp)
pipeline = argvp[0].is_a?(Array)
unless pipeline
argvv = [argvp]
else
argvv = argvp
end
command = ''
argvv.each do |argv|
bulk = nil
argv[0] = argv[0].to_s.downcase
argv[0] = ALIASES[argv[0]] if ALIASES[argv[0]]
raise "#{argv[0]} command is disabled" if DISABLED_COMMANDS[argv[0]]
if BULK_COMMANDS[argv[0]] and argv.length > 1
bulk = argv[-1].to_s
argv[-1] = bulk.respond_to?(:bytesize) ? bulk.bytesize : bulk.size
end
command << "#{argv.join(' ')}\r\n"
command << "#{bulk}\r\n" if bulk
end
@sock.write(command)
results = argvv.map do |argv|
processor = REPLY_PROCESSOR[argv[0]]
processor ? processor.call(read_reply) : read_reply
end
return pipeline ? results : results[0]
end
def select(*args)
raise "SELECT not allowed, use the :db option when creating the object"
end
def [](key)
self.get(key)
end
def []=(key,value)
set(key,value)
end
def set(key, value, expiry=nil)
s = call_command([:set, key, value]) == OK
expire(key, expiry) if s && expiry
s
end
def sort(key, options = {})
cmd = ["SORT"]
cmd << key
cmd << "BY #{options[:by]}" if options[:by]
cmd << "GET #{[options[:get]].flatten * ' GET '}" if options[:get]
cmd << "#{options[:order]}" if options[:order]
cmd << "LIMIT #{options[:limit].join(' ')}" if options[:limit]
call_command(cmd)
end
def incr(key, increment = nil)
call_command(increment ? ["incrby",key,increment] : ["incr",key])
end
def decr(key,decrement = nil)
call_command(decrement ? ["decrby",key,decrement] : ["decr",key])
end
# Similar to memcache.rb's #get_multi, returns a hash mapping
# keys to values.
def mapped_mget(*keys)
mget(*keys).inject({}) do |hash, value|
key = keys.shift
value.nil? ? hash : hash.merge(key => value)
end
end
# Ruby defines a now deprecated type method so we need to override it here
# since it will never hit method_missing
def type(key)
call_command(['type', key])
end
def quit
call_command(['quit'])
rescue Errno::ECONNRESET
end
def pipelined(&block)
pipeline = Pipeline.new self
yield pipeline
pipeline.execute
end
def read_reply
# We read the first byte using read() mainly because gets() is
# immune to raw socket timeouts.
begin
rtype = @sock.read(1)
rescue Errno::EAGAIN
# We want to make sure it reconnects on the next command after the
# timeout. Otherwise the server may reply in the meantime leaving
# the protocol in a desync status.
@sock = nil
raise Errno::EAGAIN, "Timeout reading from the socket"
end
raise Errno::ECONNRESET,"Connection lost" if !rtype
line = @sock.gets
case rtype
when MINUS
raise MINUS + line.strip
when PLUS
line.strip
when COLON
line.to_i
when DOLLAR
bulklen = line.to_i
return nil if bulklen == -1
data = @sock.read(bulklen)
@sock.read(2) # CRLF
data
when ASTERISK
objects = line.to_i
return nil if bulklen == -1
res = []
objects.times {
res << read_reply
}
res
else
raise "Protocol error, got '#{rtype}' as initial reply byte"
end
end
end
require 'rubygems'
require 'ruby-prof'
require "#{File.dirname(__FILE__)}/lib/redis"
mode = ARGV.shift || 'process_time'
n = (ARGV.shift || 200).to_i
r = Redis.new
RubyProf.measure_mode = RubyProf.const_get(mode.upcase)
RubyProf.start
n.times do |i|
key = "foo#{i}"
r[key] = key * 10
r[key]
end
results = RubyProf.stop
File.open("profile.#{mode}", 'w') do |out|
RubyProf::CallTreePrinter.new(results).print(out)
end
# -*- encoding: utf-8 -*-
Gem::Specification.new do |s|
s.name = %q{redis}
s.version = "0.1"
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
s.authors = ["Ezra Zygmuntowicz", "Taylor Weibley", "Matthew Clark", "Brian McKinney", "Salvatore Sanfilippo", "Luca Guidi"]
# s.autorequire = %q{redis-rb}
s.date = %q{2009-06-23}
s.description = %q{Ruby client library for redis key value storage server}
s.email = %q{ez@engineyard.com}
s.extra_rdoc_files = ["LICENSE"]
s.files = ["LICENSE", "README.markdown", "Rakefile", "lib/dist_redis.rb", "lib/hash_ring.rb", "lib/pipeline.rb", "lib/redis.rb", "spec/redis_spec.rb", "spec/spec_helper.rb"]
s.has_rdoc = true
s.homepage = %q{http://github.com/ezmobius/redis-rb}
s.require_paths = ["lib"]
s.rubygems_version = %q{1.3.1}
s.summary = %q{Ruby client library for redis key value storage server}
if s.respond_to? :specification_version then
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
s.specification_version = 2
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
else
end
else
end
end
require File.dirname(__FILE__) + '/spec_helper'
require 'logger'
class Foo
attr_accessor :bar
def initialize(bar)
@bar = bar
end
def ==(other)
@bar == other.bar
end
end
describe "redis" do
before(:all) do
# use database 15 for testing so we dont accidentally step on you real data
@r = Redis.new :db => 15
end
before(:each) do
@r['foo'] = 'bar'
end
after(:each) do
@r.keys('*').each {|k| @r.del k}
end
after(:all) do
@r.quit
end
it "should be able connect without a timeout" do
lambda { Redis.new :timeout => 0 }.should_not raise_error
end
it "should be able to provide a logger" do
log = StringIO.new
r = Redis.new :db => 15, :logger => Logger.new(log)
r.ping
log.string.should include("ping")
end
it "should be able to PING" do
@r.ping.should == 'PONG'
end
it "should be able to GET a key" do
@r['foo'].should == 'bar'
end
it "should be able to SET a key" do
@r['foo'] = 'nik'
@r['foo'].should == 'nik'
end
it "should properly handle trailing newline characters" do
@r['foo'] = "bar\n"
@r['foo'].should == "bar\n"
end
it "should store and retrieve all possible characters at the beginning and the end of a string" do
(0..255).each do |char_idx|
string = "#{char_idx.chr}---#{char_idx.chr}"
@r['foo'] = string
@r['foo'].should == string
end
end
it "should be able to SET a key with an expiry" do
@r.set('foo', 'bar', 1)
@r['foo'].should == 'bar'
sleep 2
@r['foo'].should == nil
end
it "should be able to return a TTL for a key" do
@r.set('foo', 'bar', 1)
@r.ttl('foo').should == 1
end
it "should be able to SETNX" do
@r['foo'] = 'nik'
@r['foo'].should == 'nik'
@r.setnx 'foo', 'bar'
@r['foo'].should == 'nik'
end
#
it "should be able to GETSET" do
@r.getset('foo', 'baz').should == 'bar'
@r['foo'].should == 'baz'
end
#
it "should be able to INCR a key" do
@r.del('counter')
@r.incr('counter').should == 1
@r.incr('counter').should == 2
@r.incr('counter').should == 3
end
#
it "should be able to INCRBY a key" do
@r.del('counter')
@r.incrby('counter', 1).should == 1
@r.incrby('counter', 2).should == 3
@r.incrby('counter', 3).should == 6
end
#
it "should be able to DECR a key" do
@r.del('counter')
@r.incr('counter').should == 1
@r.incr('counter').should == 2
@r.incr('counter').should == 3
@r.decr('counter').should == 2
@r.decr('counter', 2).should == 0
end
#
it "should be able to RANDKEY" do
@r.randkey.should_not be_nil
end
#
it "should be able to RENAME a key" do
@r.del 'foo'
@r.del'bar'
@r['foo'] = 'hi'
@r.rename 'foo', 'bar'
@r['bar'].should == 'hi'
end
#
it "should be able to RENAMENX a key" do
@r.del 'foo'
@r.del 'bar'
@r['foo'] = 'hi'
@r['bar'] = 'ohai'
@r.renamenx 'foo', 'bar'
@r['bar'].should == 'ohai'
end
#
it "should be able to get DBSIZE of the database" do
@r.delete 'foo'
dbsize_without_foo = @r.dbsize
@r['foo'] = 0
dbsize_with_foo = @r.dbsize
dbsize_with_foo.should == dbsize_without_foo + 1
end
#
it "should be able to EXPIRE a key" do
@r['foo'] = 'bar'
@r.expire 'foo', 1
@r['foo'].should == "bar"
sleep 2
@r['foo'].should == nil
end
#
it "should be able to EXISTS" do
@r['foo'] = 'nik'
@r.exists('foo').should be_true
@r.del 'foo'
@r.exists('foo').should be_false
end
#
it "should be able to KEYS" do
@r.keys("f*").each { |key| @r.del key }
@r['f'] = 'nik'
@r['fo'] = 'nak'
@r['foo'] = 'qux'
@r.keys("f*").sort.should == ['f','fo', 'foo'].sort
end
#
it "should be able to return a random key (RANDOMKEY)" do
3.times { @r.exists(@r.randomkey).should be_true }
end
#
it "should be able to check the TYPE of a key" do
@r['foo'] = 'nik'
@r.type('foo').should == "string"
@r.del 'foo'
@r.type('foo').should == "none"
end
#
it "should be able to push to the head of a list (LPUSH)" do
@r.lpush "list", 'hello'
@r.lpush "list", 42
@r.type('list').should == "list"
@r.llen('list').should == 2
@r.lpop('list').should == '42'
end
#
it "should be able to push to the tail of a list (RPUSH)" do
@r.rpush "list", 'hello'
@r.type('list').should == "list"
@r.llen('list').should == 1
end
#
it "should be able to pop the tail of a list (RPOP)" do
@r.rpush "list", 'hello'
@r.rpush"list", 'goodbye'
@r.type('list').should == "list"
@r.llen('list').should == 2
@r.rpop('list').should == 'goodbye'
end
#
it "should be able to pop the head of a list (LPOP)" do
@r.rpush "list", 'hello'
@r.rpush "list", 'goodbye'
@r.type('list').should == "list"
@r.llen('list').should == 2
@r.lpop('list').should == 'hello'
end
#
it "should be able to get the length of a list (LLEN)" do
@r.rpush "list", 'hello'
@r.rpush "list", 'goodbye'
@r.type('list').should == "list"
@r.llen('list').should == 2
end
#
it "should be able to get a range of values from a list (LRANGE)" do
@r.rpush "list", 'hello'
@r.rpush "list", 'goodbye'
@r.rpush "list", '1'
@r.rpush "list", '2'
@r.rpush "list", '3'
@r.type('list').should == "list"
@r.llen('list').should == 5
@r.lrange('list', 2, -1).should == ['1', '2', '3']
end
#
it "should be able to trim a list (LTRIM)" do
@r.rpush "list", 'hello'
@r.rpush "list", 'goodbye'
@r.rpush "list", '1'
@r.rpush "list", '2'
@r.rpush "list", '3'
@r.type('list').should == "list"
@r.llen('list').should == 5
@r.ltrim 'list', 0, 1
@r.llen('list').should == 2
@r.lrange('list', 0, -1).should == ['hello', 'goodbye']
end
#
it "should be able to get a value by indexing into a list (LINDEX)" do
@r.rpush "list", 'hello'
@r.rpush "list", 'goodbye'
@r.type('list').should == "list"
@r.llen('list').should == 2
@r.lindex('list', 1).should == 'goodbye'
end
#
it "should be able to set a value by indexing into a list (LSET)" do
@r.rpush "list", 'hello'
@r.rpush "list", 'hello'
@r.type('list').should == "list"
@r.llen('list').should == 2
@r.lset('list', 1, 'goodbye').should == 'OK'
@r.lindex('list', 1).should == 'goodbye'
end
#
it "should be able to remove values from a list (LREM)" do
@r.rpush "list", 'hello'
@r.rpush "list", 'goodbye'
@r.type('list').should == "list"
@r.llen('list').should == 2
@r.lrem('list', 1, 'hello').should == 1
@r.lrange('list', 0, -1).should == ['goodbye']
end
#
it "should be able add members to a set (SADD)" do
@r.sadd "set", 'key1'
@r.sadd "set", 'key2'
@r.type('set').should == "set"
@r.scard('set').should == 2
@r.smembers('set').sort.should == ['key1', 'key2'].sort
end
#
it "should be able delete members to a set (SREM)" do
@r.sadd "set", 'key1'
@r.sadd "set", 'key2'
@r.type('set').should == "set"
@r.scard('set').should == 2
@r.smembers('set').sort.should == ['key1', 'key2'].sort
@r.srem('set', 'key1')
@r.scard('set').should == 1
@r.smembers('set').should == ['key2']
end
#
it "should be able count the members of a set (SCARD)" do
@r.sadd "set", 'key1'
@r.sadd "set", 'key2'
@r.type('set').should == "set"
@r.scard('set').should == 2
end
#
it "should be able test for set membership (SISMEMBER)" do
@r.sadd "set", 'key1'
@r.sadd "set", 'key2'
@r.type('set').should == "set"
@r.scard('set').should == 2
@r.sismember('set', 'key1').should be_true
@r.sismember('set', 'key2').should be_true
@r.sismember('set', 'notthere').should be_false
end
#
it "should be able to do set intersection (SINTER)" do
@r.sadd "set", 'key1'
@r.sadd "set", 'key2'
@r.sadd "set2", 'key2'
@r.sinter('set', 'set2').should == ['key2']
end
#
it "should be able to do set intersection and store the results in a key (SINTERSTORE)" do
@r.sadd "set", 'key1'
@r.sadd "set", 'key2'
@r.sadd "set2", 'key2'
@r.sinterstore('newone', 'set', 'set2').should == 1
@r.smembers('newone').should == ['key2']
end
#
it "should be able to do set union (SUNION)" do
@r.sadd "set", 'key1'
@r.sadd "set", 'key2'
@r.sadd "set2", 'key2'
@r.sadd "set2", 'key3'
@r.sunion('set', 'set2').sort.should == ['key1','key2','key3'].sort
end
#
it "should be able to do set union and store the results in a key (SUNIONSTORE)" do
@r.sadd "set", 'key1'
@r.sadd "set", 'key2'
@r.sadd "set2", 'key2'
@r.sadd "set2", 'key3'
@r.sunionstore('newone', 'set', 'set2').should == 3
@r.smembers('newone').sort.should == ['key1','key2','key3'].sort
end
#
it "should be able to do set difference (SDIFF)" do
@r.sadd "set", 'a'
@r.sadd "set", 'b'
@r.sadd "set2", 'b'
@r.sadd "set2", 'c'
@r.sdiff('set', 'set2').should == ['a']
end
#
it "should be able to do set difference and store the results in a key (SDIFFSTORE)" do
@r.sadd "set", 'a'
@r.sadd "set", 'b'
@r.sadd "set2", 'b'
@r.sadd "set2", 'c'
@r.sdiffstore('newone', 'set', 'set2')
@r.smembers('newone').should == ['a']
end
#
it "should be able move elements from one set to another (SMOVE)" do
@r.sadd 'set1', 'a'
@r.sadd 'set1', 'b'
@r.sadd 'set2', 'x'
@r.smove('set1', 'set2', 'a').should be_true
@r.sismember('set2', 'a').should be_true
@r.delete('set1')
end
#
it "should be able to do crazy SORT queries" do
# The 'Dogs' is capitialized on purpose
@r['dog_1'] = 'louie'
@r.rpush 'Dogs', 1
@r['dog_2'] = 'lucy'
@r.rpush 'Dogs', 2
@r['dog_3'] = 'max'
@r.rpush 'Dogs', 3
@r['dog_4'] = 'taj'
@r.rpush 'Dogs', 4
@r.sort('Dogs', :get => 'dog_*', :limit => [0,1]).should == ['louie']
@r.sort('Dogs', :get => 'dog_*', :limit => [0,1], :order => 'desc alpha').should == ['taj']
end
it "should be able to handle array of :get using SORT" do
@r['dog:1:name'] = 'louie'
@r['dog:1:breed'] = 'mutt'
@r.rpush 'dogs', 1
@r['dog:2:name'] = 'lucy'
@r['dog:2:breed'] = 'poodle'
@r.rpush 'dogs', 2
@r['dog:3:name'] = 'max'
@r['dog:3:breed'] = 'hound'
@r.rpush 'dogs', 3
@r['dog:4:name'] = 'taj'
@r['dog:4:breed'] = 'terrier'
@r.rpush 'dogs', 4
@r.sort('dogs', :get => ['dog:*:name', 'dog:*:breed'], :limit => [0,1]).should == ['louie', 'mutt']
@r.sort('dogs', :get => ['dog:*:name', 'dog:*:breed'], :limit => [0,1], :order => 'desc alpha').should == ['taj', 'terrier']
end
#
it "should provide info (INFO)" do
[:last_save_time, :redis_version, :total_connections_received, :connected_clients, :total_commands_processed, :connected_slaves, :uptime_in_seconds, :used_memory, :uptime_in_days, :changes_since_last_save].each do |x|
@r.info.keys.should include(x)
end
end
#
it "should be able to flush the database (FLUSHDB)" do
@r['key1'] = 'keyone'
@r['key2'] = 'keytwo'
@r.keys('*').sort.should == ['foo', 'key1', 'key2'].sort #foo from before
@r.flushdb
@r.keys('*').should == []
end
#
it "should raise exception when manually try to change the database" do
lambda { @r.select(0) }.should raise_error
end
#
it "should be able to provide the last save time (LASTSAVE)" do
savetime = @r.lastsave
Time.at(savetime).class.should == Time
Time.at(savetime).should <= Time.now
end
it "should be able to MGET keys" do
@r['foo'] = 1000
@r['bar'] = 2000
@r.mget('foo', 'bar').should == ['1000', '2000']
@r.mget('foo', 'bar', 'baz').should == ['1000', '2000', nil]
end
it "should be able to mapped MGET keys" do
@r['foo'] = 1000
@r['bar'] = 2000
@r.mapped_mget('foo', 'bar').should == { 'foo' => '1000', 'bar' => '2000'}
@r.mapped_mget('foo', 'baz', 'bar').should == { 'foo' => '1000', 'bar' => '2000'}
end
it "should bgsave" do
@r.bgsave.should == 'OK'
end
it "should be able to ECHO" do
@r.echo("message in a bottle\n").should == "message in a bottle\n"
end
it "should raise error when invoke MONITOR" do
lambda { @r.monitor }.should raise_error
end
it "should raise error when invoke SYNC" do
lambda { @r.sync }.should raise_error
end
it "should handle multiple servers" do
require 'dist_redis'
@r = DistRedis.new(:hosts=> ['localhost:6379', '127.0.0.1:6379'], :db => 15)
100.times do |idx|
@r[idx] = "foo#{idx}"
end
100.times do |idx|
@r[idx].should == "foo#{idx}"
end
end
it "should be able to pipeline writes" do
@r.pipelined do |pipeline|
pipeline.lpush 'list', "hello"
pipeline.lpush 'list', 42
end
@r.type('list').should == "list"
@r.llen('list').should == 2
@r.lpop('list').should == '42'
end
it "should AUTH when connecting with a password" do
r = Redis.new(:password => 'secret')
r.stub!(:connect_to)
r.should_receive(:call_command).with(['auth', 'secret'])
r.connect_to_server
end
end
require 'rubygems'
$TESTING=true
$:.unshift File.join(File.dirname(__FILE__), '..', 'lib')
require 'redis'
require 'benchmark'
require "#{File.dirname(__FILE__)}/lib/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)
# Inspired by rabbitmq.rake the Redbox project at http://github.com/rick/redbox/tree/master
require 'fileutils'
require 'open-uri'
class RedisRunner
def self.redisdir
"/tmp/redis/"
end
def self.redisconfdir
'/etc/redis.conf'
end
def self.dtach_socket
'/tmp/redis.dtach'
end
# Just check for existance of dtach socket
def self.running?
File.exists? dtach_socket
end
def self.start
puts 'Detach with Ctrl+\ Re-attach with rake redis:attach'
sleep 3
exec "dtach -A #{dtach_socket} redis-server #{redisconfdir}"
end
def self.attach
exec "dtach -a #{dtach_socket}"
end
def self.stop
sh 'echo "SHUTDOWN" | nc localhost 6379'
end
end
namespace :redis do
desc 'About redis'
task :about do
puts "\nSee http://code.google.com/p/redis/ for information about redis.\n\n"
end
desc 'Start redis'
task :start do
RedisRunner.start
end
desc 'Stop redis'
task :stop do
RedisRunner.stop
end
desc 'Restart redis'
task :restart do
RedisRunner.stop
RedisRunner.start
end
desc 'Attach to redis dtach socket'
task :attach do
RedisRunner.attach
end
desc 'Install the lastest verison of Redis from Github (requires git, duh)'
task :install => [:about, :download, :make] do
%w(redis-benchmark redis-cli redis-server).each do |bin|
sh "sudo cp /tmp/redis/#{bin} /usr/bin/"
end
puts "Installed redis-benchmark, redis-cli and redis-server to /usr/bin/"
unless File.exists?('/etc/redis.conf')
sh 'sudo cp /tmp/redis/redis.conf /etc/'
puts "Installed redis.conf to /etc/ \n You should look at this file!"
end
end
task :make do
sh "cd #{RedisRunner.redisdir} && make clean"
sh "cd #{RedisRunner.redisdir} && make"
end
desc "Download package"
task :download do
sh 'rm -rf /tmp/redis/' if File.exists?("#{RedisRunner.redisdir}/.svn")
sh 'git clone git://github.com/antirez/redis.git /tmp/redis' unless File.exists?(RedisRunner.redisdir)
sh "cd #{RedisRunner.redisdir} && git pull" if File.exists?("#{RedisRunner.redisdir}/.git")
end
end
namespace :dtach do
desc 'About dtach'
task :about do
puts "\nSee http://dtach.sourceforge.net/ for information about dtach.\n\n"
end
desc 'Install dtach 0.8 from source'
task :install => [:about] do
Dir.chdir('/tmp/')
unless File.exists?('/tmp/dtach-0.8.tar.gz')
require 'net/http'
url = 'http://downloads.sourceforge.net/project/dtach/dtach/0.8/dtach-0.8.tar.gz'
open('/tmp/dtach-0.8.tar.gz', 'wb') do |file| file.write(open(url).read) end
end
unless File.directory?('/tmp/dtach-0.8')
system('tar xzf dtach-0.8.tar.gz')
end
Dir.chdir('/tmp/dtach-0.8/')
sh 'cd /tmp/dtach-0.8/ && ./configure && make'
sh 'sudo cp /tmp/dtach-0.8/dtach /usr/bin/'
puts 'Dtach successfully installed to /usr/bin.'
end
end
.DS_Store
lib_managed
project/boot
target
target/
target/**/*
# Redis Scala client
## Key features of the library
- Native Scala types Set and List responses.
- Consisten Hashing on the client.
- Support for Clustering of Redis nodes.
## Information about redis
Redis is a key-value database. It is similar to memcached but the dataset is not volatile, and values can be strings, exactly like in memcached, but also lists and sets with atomic operations to push/pop elements.
http://code.google.com/p/redis/
### Key features of Redis
- Fast in-memory store with asynchronous save to disk.
- Key value get, set, delete, etc.
- Atomic operations on sets and lists, union, intersection, trim, etc.
## Requirements
- sbt (get it at http://code.google.com/p/simple-build-tool/)
## Usage
Start your redis instance (usually redis-server will do it)
$ cd scala-redis
$ sbt
> update
> test (optional to run the tests)
> console
And you are ready to start issuing commands to the server(s)
let's connect and get a key:
scala> import com.redis._
scala> val r = new Redis("localhost", 6379)
scala> val r.set("key", "some value")
scala> val r.get("key")
Alejandro Crosa <<alejandrocrosa@gmail.com>>
#Project properties
#Wed Aug 19 07:54:05 ART 2009
project.organization=com.redis
project.name=RedisClient
sbt.version=0.5.1
project.version=1.0.1
scala.version=2.7.5
project.initialize=false
import sbt._
class RedisClientProject(info: ProjectInfo) extends DefaultProject(info) with AutoCompilerPlugins
{
override def useDefaultConfigurations = true
val scalatest = "org.scala-tools.testing" % "scalatest" % "0.9.5" % "test->default"
val specs = "org.scala-tools.testing" % "specs" % "1.5.0"
val mockito = "org.mockito" % "mockito-all" % "1.7"
val junit = "junit" % "junit" % "4.5"
val sxr = compilerPlugin("org.scala-tools.sxr" %% "sxr" % "0.2.1")
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment