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

client libs removed from Redis git

parent 5762b7f0
Redis
Perl binding for Redis database which is in-memory hash store with
support for scalars, arrays and sets and disk persistence.
INSTALLATION
To install this module, run the following commands:
perl Makefile.PL
make
make test
make install
SUPPORT AND DOCUMENTATION
After installing, you can find documentation for this module with the
perldoc command.
perldoc Redis
You can also look for information at:
RT, CPAN's request tracker
http://rt.cpan.org/NoAuth/Bugs.html?Dist=Redis
AnnoCPAN, Annotated CPAN documentation
http://annocpan.org/dist/Redis
CPAN Ratings
http://cpanratings.perl.org/d/Redis
Search CPAN
http://search.cpan.org/dist/Redis
COPYRIGHT AND LICENCE
Copyright (C) 2009 Dobrica Pavlinusic
This program is free software; you can redistribute it and/or modify it
under the same terms as Perl itself.
package Redis;
use warnings;
use strict;
use IO::Socket::INET;
use Data::Dump qw/dump/;
use Carp qw/confess/;
=head1 NAME
Redis - perl binding for Redis database
=cut
our $VERSION = '0.08';
=head1 DESCRIPTION
Pure perl bindings for L<http://code.google.com/p/redis/>
This version support git version 0.08 of Redis available at
L<git://github.com/antirez/redis>
This documentation
lists commands which are exercised in test suite, but
additinal commands will work correctly since protocol
specifies enough information to support almost all commands
with same peace of code with a little help of C<AUTOLOAD>.
=head1 FUNCTIONS
=head2 new
my $r = Redis->new;
=cut
our $debug = $ENV{REDIS} || 0;
our $sock;
my $server = '127.0.0.1:6379';
sub new {
my $class = shift;
my $self = {};
bless($self, $class);
warn "# opening socket to $server";
$sock ||= IO::Socket::INET->new(
PeerAddr => $server,
Proto => 'tcp',
) || die $!;
$self;
}
my $bulk_command = {
set => 1, setnx => 1,
rpush => 1, lpush => 1,
lset => 1, lrem => 1,
sadd => 1, srem => 1,
sismember => 1,
echo => 1,
};
# we don't want DESTROY to fallback into AUTOLOAD
sub DESTROY {}
our $AUTOLOAD;
sub AUTOLOAD {
my $self = shift;
my $command = $AUTOLOAD;
$command =~ s/.*://;
warn "## $command ",dump(@_) if $debug;
my $send;
if ( defined $bulk_command->{$command} ) {
my $value = pop;
$value = '' if ! defined $value;
$send
= uc($command)
. ' '
. join(' ', @_)
. ' '
. length( $value )
. "\r\n$value\r\n"
;
} else {
$send
= uc($command)
. ' '
. join(' ', @_)
. "\r\n"
;
}
warn ">> $send" if $debug;
print $sock $send;
if ( $command eq 'quit' ) {
close( $sock ) || die "can't close socket: $!";
return 1;
}
my $result = <$sock> || die "can't read socket: $!";
warn "<< $result" if $debug;
my $type = substr($result,0,1);
$result = substr($result,1,-2);
if ( $command eq 'info' ) {
my $hash;
foreach my $l ( split(/\r\n/, __sock_read_bulk($result) ) ) {
my ($n,$v) = split(/:/, $l, 2);
$hash->{$n} = $v;
}
return $hash;
} elsif ( $command eq 'keys' ) {
my $keys = __sock_read_bulk($result);
return split(/\s/, $keys) if $keys;
return;
}
if ( $type eq '-' ) {
confess $result;
} elsif ( $type eq '+' ) {
return $result;
} elsif ( $type eq '$' ) {
return __sock_read_bulk($result);
} elsif ( $type eq '*' ) {
return __sock_read_multi_bulk($result);
} elsif ( $type eq ':' ) {
return $result; # FIXME check if int?
} else {
confess "unknown type: $type", __sock_read_line();
}
}
sub __sock_read_bulk {
my $len = shift;
return undef if $len < 0;
my $v;
if ( $len > 0 ) {
read($sock, $v, $len) || die $!;
warn "<< ",dump($v),$/ if $debug;
}
my $crlf;
read($sock, $crlf, 2); # skip cr/lf
return $v;
}
sub __sock_read_multi_bulk {
my $size = shift;
return undef if $size < 0;
$size--;
my @list = ( 0 .. $size );
foreach ( 0 .. $size ) {
$list[ $_ ] = __sock_read_bulk( substr(<$sock>,1,-2) );
}
warn "## list = ", dump( @list ) if $debug;
return @list;
}
1;
__END__
=head1 Connection Handling
=head2 quit
$r->quit;
=head2 ping
$r->ping || die "no server?";
=head1 Commands operating on string values
=head2 set
$r->set( foo => 'bar' );
$r->setnx( foo => 42 );
=head2 get
my $value = $r->get( 'foo' );
=head2 mget
my @values = $r->mget( 'foo', 'bar', 'baz' );
=head2 incr
$r->incr('counter');
$r->incrby('tripplets', 3);
=head2 decr
$r->decr('counter');
$r->decrby('tripplets', 3);
=head2 exists
$r->exists( 'key' ) && print "got key!";
=head2 del
$r->del( 'key' ) || warn "key doesn't exist";
=head2 type
$r->type( 'key' ); # = string
=head1 Commands operating on the key space
=head2 keys
my @keys = $r->keys( '*glob_pattern*' );
=head2 randomkey
my $key = $r->randomkey;
=head2 rename
my $ok = $r->rename( 'old-key', 'new-key', $new );
=head2 dbsize
my $nr_keys = $r->dbsize;
=head1 Commands operating on lists
See also L<Redis::List> for tie interface.
=head2 rpush
$r->rpush( $key, $value );
=head2 lpush
$r->lpush( $key, $value );
=head2 llen
$r->llen( $key );
=head2 lrange
my @list = $r->lrange( $key, $start, $end );
=head2 ltrim
my $ok = $r->ltrim( $key, $start, $end );
=head2 lindex
$r->lindex( $key, $index );
=head2 lset
$r->lset( $key, $index, $value );
=head2 lrem
my $modified_count = $r->lrem( $key, $count, $value );
=head2 lpop
my $value = $r->lpop( $key );
=head2 rpop
my $value = $r->rpop( $key );
=head1 Commands operating on sets
=head2 sadd
$r->sadd( $key, $member );
=head2 srem
$r->srem( $key, $member );
=head2 scard
my $elements = $r->scard( $key );
=head2 sismember
$r->sismember( $key, $member );
=head2 sinter
$r->sinter( $key1, $key2, ... );
=head2 sinterstore
my $ok = $r->sinterstore( $dstkey, $key1, $key2, ... );
=head1 Multiple databases handling commands
=head2 select
$r->select( $dbindex ); # 0 for new clients
=head2 move
$r->move( $key, $dbindex );
=head2 flushdb
$r->flushdb;
=head2 flushall
$r->flushall;
=head1 Sorting
=head2 sort
$r->sort("key BY pattern LIMIT start end GET pattern ASC|DESC ALPHA');
=head1 Persistence control commands
=head2 save
$r->save;
=head2 bgsave
$r->bgsave;
=head2 lastsave
$r->lastsave;
=head2 shutdown
$r->shutdown;
=head1 Remote server control commands
=head2 info
my $info_hash = $r->info;
=head1 AUTHOR
Dobrica Pavlinusic, C<< <dpavlin at rot13.org> >>
=head1 BUGS
Please report any bugs or feature requests to C<bug-redis at rt.cpan.org>, or through
the web interface at L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Redis>. I will be notified, and then you'll
automatically be notified of progress on your bug as I make changes.
=head1 SUPPORT
You can find documentation for this module with the perldoc command.
perldoc Redis
perldoc Redis::List
perldoc Redis::Hash
You can also look for information at:
=over 4
=item * RT: CPAN's request tracker
L<http://rt.cpan.org/NoAuth/Bugs.html?Dist=Redis>
=item * AnnoCPAN: Annotated CPAN documentation
L<http://annocpan.org/dist/Redis>
=item * CPAN Ratings
L<http://cpanratings.perl.org/d/Redis>
=item * Search CPAN
L<http://search.cpan.org/dist/Redis>
=back
=head1 ACKNOWLEDGEMENTS
=head1 COPYRIGHT & LICENSE
Copyright 2009 Dobrica Pavlinusic, all rights reserved.
This program is free software; you can redistribute it and/or modify it
under the same terms as Perl itself.
=cut
1; # End of Redis
package Redis::Hash;
use strict;
use warnings;
use Tie::Hash;
use base qw/Redis Tie::StdHash/;
use Data::Dump qw/dump/;
=head1 NAME
Redis::Hash - tie perl hashes into Redis
=head1 SYNOPSYS
tie %name, 'Redis::Hash', 'prefix';
=cut
# mandatory methods
sub TIEHASH {
my ($class,$name) = @_;
my $self = Redis->new;
$name .= ':' if $name;
$self->{name} = $name || '';
bless $self => $class;
}
sub STORE {
my ($self,$key,$value) = @_;
$self->set( $self->{name} . $key, $value );
}
sub FETCH {
my ($self,$key) = @_;
$self->get( $self->{name} . $key );
}
sub FIRSTKEY {
my $self = shift;
$self->{keys} = [ $self->keys( $self->{name} . '*' ) ];
$self->NEXTKEY;
}
sub NEXTKEY {
my $self = shift;
my $key = shift @{ $self->{keys} } || return;
my $name = $self->{name};
$key =~ s{^$name}{} || warn "can't strip $name from $key";
return $key;
}
sub EXISTS {
my ($self,$key) = @_;
$self->exists( $self->{name} . $key );
}
sub DELETE {
my ($self,$key) = @_;
$self->del( $self->{name} . $key );
}
sub CLEAR {
my ($self) = @_;
$self->del( $_ ) foreach ( $self->keys( $self->{name} . '*' ) );
$self->{keys} = [];
}
1;
package Redis::List;
use strict;
use warnings;
use base qw/Redis Tie::Array/;
=head1 NAME
Redis::List - tie perl arrays into Redis lists
=head1 SYNOPSYS
tie @a, 'Redis::List', 'name';
=cut
# mandatory methods
sub TIEARRAY {
my ($class,$name) = @_;
my $self = $class->new;
$self->{name} = $name;
bless $self => $class;
}
sub FETCH {
my ($self,$index) = @_;
$self->lindex( $self->{name}, $index );
}
sub FETCHSIZE {
my ($self) = @_;
$self->llen( $self->{name} );
}
sub STORE {
my ($self,$index,$value) = @_;
$self->lset( $self->{name}, $index, $value );
}
sub STORESIZE {
my ($self,$count) = @_;
$self->ltrim( $self->{name}, 0, $count );
# if $count > $self->FETCHSIZE;
}
sub CLEAR {
my ($self) = @_;
$self->del( $self->{name} );
}
sub PUSH {
my $self = shift;
$self->rpush( $self->{name}, $_ ) foreach @_;
}
sub SHIFT {
my $self = shift;
$self->lpop( $self->{name} );
}
sub UNSHIFT {
my $self = shift;
$self->lpush( $self->{name}, $_ ) foreach @_;
}
sub SPLICE {
my $self = shift;
my $offset = shift;
my $length = shift;
$self->lrange( $self->{name}, $offset, $length );
# FIXME rest of @_ ?
}
sub EXTEND {
my ($self,$count) = @_;
$self->rpush( $self->{name}, '' ) foreach ( $self->FETCHSIZE .. ( $count - 1 ) );
}
sub DESTROY {
my $self = shift;
$self->quit;
}
1;
#!/usr/bin/perl
use warnings;
use strict;
use Benchmark qw/:all/;
use lib 'lib';
use Redis;
my $r = Redis->new;
my $i = 0;
timethese( 100000, {
'00_ping' => sub { $r->ping },
'10_set' => sub { $r->set( 'foo', $i++ ) },
'11_set_r' => sub { $r->set( 'bench-' . rand(), rand() ) },
'20_get' => sub { $r->get( 'foo' ) },
'21_get_r' => sub { $r->get( 'bench-' . rand() ) },
'30_incr' => sub { $r->incr( 'counter' ) },
'30_incr_r' => sub { $r->incr( 'bench-' . rand() ) },
'40_lpush' => sub { $r->lpush( 'mylist', 'bar' ) },
'40_lpush' => sub { $r->lpush( 'mylist', 'bar' ) },
'50_lpop' => sub { $r->lpop( 'mylist' ) },
});
#!perl -T
use Test::More tests => 1;
BEGIN {
use_ok( 'Redis' );
}
diag( "Testing Redis $Redis::VERSION, Perl $], $^X" );
#!/usr/bin/perl
use warnings;
use strict;
use Test::More tests => 106;
use Data::Dump qw/dump/;
use lib 'lib';
BEGIN {
use_ok( 'Redis' );
}
ok( my $o = Redis->new(), 'new' );
ok( $o->ping, 'ping' );
diag "Commands operating on string values";
ok( $o->set( foo => 'bar' ), 'set foo => bar' );
ok( ! $o->setnx( foo => 'bar' ), 'setnx foo => bar fails' );
cmp_ok( $o->get( 'foo' ), 'eq', 'bar', 'get foo = bar' );
ok( $o->set( foo => 'baz' ), 'set foo => baz' );
cmp_ok( $o->get( 'foo' ), 'eq', 'baz', 'get foo = baz' );
ok( $o->set( 'test-undef' => 42 ), 'set test-undef' );
ok( $o->set( 'test-undef' => undef ), 'set undef' );
ok( ! defined $o->get( 'test-undef' ), 'get undef' );
ok( $o->exists( 'test-undef' ), 'exists undef' );
$o->del('non-existant');
ok( ! $o->exists( 'non-existant' ), 'exists non-existant' );
ok( ! $o->get( 'non-existant' ), 'get non-existant' );
ok( $o->set('key-next' => 0), 'key-next = 0' );
my $key_next = 3;
ok( $o->set('key-left' => $key_next), 'key-left' );
is_deeply( [ $o->mget( 'foo', 'key-next', 'key-left' ) ], [ 'baz', 0, 3 ], 'mget' );
my @keys;
foreach my $id ( 0 .. $key_next ) {
my $key = 'key-' . $id;
push @keys, $key;
ok( $o->set( $key => $id ), "set $key" );
ok( $o->exists( $key ), "exists $key" );
cmp_ok( $o->get( $key ), 'eq', $id, "get $key" );
cmp_ok( $o->incr( 'key-next' ), '==', $id + 1, 'incr' );
cmp_ok( $o->decr( 'key-left' ), '==', $key_next - $id - 1, 'decr' );
}
cmp_ok( $o->get( 'key-next' ), '==', $key_next + 1, 'key-next' );
ok( $o->set('test-incrby', 0), 'test-incrby' );
ok( $o->set('test-decrby', 0), 'test-decry' );
foreach ( 1 .. 3 ) {
cmp_ok( $o->incrby('test-incrby', 3), '==', $_ * 3, 'incrby 3' );
cmp_ok( $o->decrby('test-decrby', 7), '==', -( $_ * 7 ), 'decrby 7' );
}
ok( $o->del( $_ ), "del $_" ) foreach map { "key-$_" } ( 'next', 'left' );
ok( ! $o->del('non-existing' ), 'del non-existing' );
cmp_ok( $o->type('foo'), 'eq', 'string', 'type' );
cmp_ok( $o->keys('key-*'), '==', $key_next + 1, 'key-*' );
is_deeply( [ $o->keys('key-*') ], [ @keys ], 'keys' );
ok( my $key = $o->randomkey, 'randomkey' );
ok( $o->rename( 'test-incrby', 'test-renamed' ), 'rename' );
ok( $o->exists( 'test-renamed' ), 'exists test-renamed' );
eval { $o->rename( 'test-decrby', 'test-renamed', 1 ) };
ok( $@, 'rename to existing key' );
ok( my $nr_keys = $o->dbsize, 'dbsize' );
diag "Commands operating on lists";
my $list = 'test-list';
$o->del($list) && diag "cleanup $list from last run";
ok( $o->rpush( $list => "r$_" ), 'rpush' ) foreach ( 1 .. 3 );
ok( $o->lpush( $list => "l$_" ), 'lpush' ) foreach ( 1 .. 2 );
cmp_ok( $o->type($list), 'eq', 'list', 'type' );
cmp_ok( $o->llen($list), '==', 5, 'llen' );
is_deeply( [ $o->lrange( $list, 0, 1 ) ], [ 'l2', 'l1' ], 'lrange' );
ok( $o->ltrim( $list, 1, 2 ), 'ltrim' );
cmp_ok( $o->llen($list), '==', 2, 'llen after ltrim' );
cmp_ok( $o->lindex( $list, 0 ), 'eq', 'l1', 'lindex' );
cmp_ok( $o->lindex( $list, 1 ), 'eq', 'r1', 'lindex' );
ok( $o->lset( $list, 0, 'foo' ), 'lset' );
cmp_ok( $o->lindex( $list, 0 ), 'eq', 'foo', 'verified' );
ok( $o->lrem( $list, 1, 'foo' ), 'lrem' );
cmp_ok( $o->llen( $list ), '==', 1, 'llen after lrem' );
cmp_ok( $o->lpop( $list ), 'eq', 'r1', 'lpop' );
ok( ! $o->rpop( $list ), 'rpop' );
diag "Commands operating on sets";
my $set = 'test-set';
$o->del($set);
ok( $o->sadd( $set, 'foo' ), 'sadd' );
ok( ! $o->sadd( $set, 'foo' ), 'sadd' );
cmp_ok( $o->scard( $set ), '==', 1, 'scard' );
ok( $o->sismember( $set, 'foo' ), 'sismember' );
cmp_ok( $o->type( $set ), 'eq', 'set', 'type is set' );
ok( $o->srem( $set, 'foo' ), 'srem' );
ok( ! $o->srem( $set, 'foo' ), 'srem again' );
cmp_ok( $o->scard( $set ), '==', 0, 'scard' );
$o->sadd( 'test-set1', $_ ) foreach ( 'foo', 'bar', 'baz' );
$o->sadd( 'test-set2', $_ ) foreach ( 'foo', 'baz', 'xxx' );
my $inter = [ 'baz', 'foo' ];
is_deeply( [ $o->sinter( 'test-set1', 'test-set2' ) ], $inter, 'siter' );
ok( $o->sinterstore( 'test-set-inter', 'test-set1', 'test-set2' ), 'sinterstore' );
cmp_ok( $o->scard( 'test-set-inter' ), '==', $#$inter + 1, 'cardinality of intersection' );
diag "Multiple databases handling commands";
ok( $o->select( 1 ), 'select' );
ok( $o->select( 0 ), 'select' );
ok( $o->move( 'foo', 1 ), 'move' );
ok( ! $o->exists( 'foo' ), 'gone' );
ok( $o->select( 1 ), 'select' );
ok( $o->exists( 'foo' ), 'exists' );
ok( $o->flushdb, 'flushdb' );
cmp_ok( $o->dbsize, '==', 0, 'empty' );
diag "Sorting";
ok( $o->lpush( 'test-sort', $_ ), "put $_" ) foreach ( 1 .. 4 );
cmp_ok( $o->llen( 'test-sort' ), '==', 4, 'llen' );
is_deeply( [ $o->sort( 'test-sort' ) ], [ 1,2,3,4 ], 'sort' );
is_deeply( [ $o->sort( 'test-sort DESC' ) ], [ 4,3,2,1 ], 'sort DESC' );
diag "Persistence control commands";
ok( $o->save, 'save' );
ok( $o->bgsave, 'bgsave' );
ok( $o->lastsave, 'lastsave' );
#ok( $o->shutdown, 'shutdown' );
diag "shutdown not tested";
diag "Remote server control commands";
ok( my $info = $o->info, 'info' );
diag dump( $info );
diag "Connection handling";
ok( $o->quit, 'quit' );
#!/usr/bin/perl
use warnings;
use strict;
use Test::More tests => 8;
use lib 'lib';
use Data::Dump qw/dump/;
BEGIN {
use_ok( 'Redis::List' );
}
my @a;
ok( my $o = tie( @a, 'Redis::List', 'test-redis-list' ), 'tie' );
isa_ok( $o, 'Redis::List' );
$o->CLEAR;
ok( ! @a, 'empty list' );
ok( @a = ( 'foo', 'bar', 'baz' ), '=' );
is_deeply( [ @a ], [ 'foo', 'bar', 'baz' ] );
ok( push( @a, 'push' ), 'push' );
is_deeply( [ @a ], [ 'foo', 'bar', 'baz', 'push' ] );
#diag dump( @a );
#!/usr/bin/perl
use warnings;
use strict;
use Test::More tests => 7;
use lib 'lib';
use Data::Dump qw/dump/;
BEGIN {
use_ok( 'Redis::Hash' );
}
ok( my $o = tie( my %h, 'Redis::Hash', 'test-redis-hash' ), 'tie' );
isa_ok( $o, 'Redis::Hash' );
$o->CLEAR();
ok( ! keys %h, 'empty' );
ok( %h = ( 'foo' => 42, 'bar' => 1, 'baz' => 99 ), '=' );
is_deeply( [ sort keys %h ], [ 'bar', 'baz', 'foo' ], 'keys' );
is_deeply( \%h, { bar => 1, baz => 99, foo => 42, }, 'structure' );
#diag dump( \%h );
use strict;
use warnings;
use Test::More;
# Ensure a recent version of Test::Pod::Coverage
my $min_tpc = 1.08;
eval "use Test::Pod::Coverage $min_tpc";
plan skip_all => "Test::Pod::Coverage $min_tpc required for testing POD coverage"
if $@;
# Test::Pod::Coverage doesn't require a minimum Pod::Coverage version,
# but older versions don't recognize some common documentation styles
my $min_pc = 0.18;
eval "use Pod::Coverage $min_pc";
plan skip_all => "Pod::Coverage $min_pc required for testing POD coverage"
if $@;
all_pod_coverage_ok();
#!perl -T
use strict;
use warnings;
use Test::More;
# Ensure a recent version of Test::Pod
my $min_tp = 1.22;
eval "use Test::Pod $min_tp";
plan skip_all => "Test::Pod $min_tp required for testing POD" if $@;
all_pod_files_ok();
<?php
/*******************************************************************************
* Redis PHP Bindings - http://code.google.com/p/redis/
*
* Copyright 2009 Ludovico Magnocavallo
* Copyright 2009 Salvatore Sanfilippo (ported it to PHP5, fixed some bug)
* Released under the same license as Redis.
*
* Version: 0.1
*
* $Revision: 139 $
* $Date: 2009-03-15 22:59:40 +0100 (Dom, 15 Mar 2009) $
*
******************************************************************************/
class Redis {
public $server;
public $port;
private $_sock;
public function __construct($host='localhost', $port=6379) {
$this->host = $host;
$this->port = $port;
}
public function connect() {
if ($this->_sock) return;
if ($sock = fsockopen($this->host, $this->port, $errno, $errstr)) {
$this->_sock = $sock;
return;
}
$msg = "Cannot open socket to {$this->host}:{$this->port}";
if ($errno || $errmsg)
$msg .= "," . ($errno ? " error $errno" : "") .
($errmsg ? " $errmsg" : "");
trigger_error("$msg.", E_USER_ERROR);
}
public function disconnect() {
if ($this->_sock) @fclose($this->_sock);
$this->_sock = null;
}
public function ping() {
$this->connect();
$this->write("PING\r\n");
return $this->get_response();
}
public function do_echo($s) {
$this->connect();
$this->write("ECHO " . strlen($s) . "\r\n$s\r\n");
return $this->get_response();
}
public function set($name, $value, $preserve=false) {
$this->connect();
$this->write(
($preserve ? 'SETNX' : 'SET') .
" $name " . strlen($value) . "\r\n$value\r\n"
);
return $this->get_response();
}
public function get($name) {
$this->connect();
$this->write("GET $name\r\n");
return $this->get_response();
}
public function mget($keys) {
$this->connect();
$this->write("MGET ".implode(" ",$keys)."\r\n");
return $this->get_response();
}
public function incr($name, $amount=1) {
$this->connect();
if ($amount == 1)
$this->write("INCR $name\r\n");
else
$this->write("INCRBY $name $amount\r\n");
return $this->get_response();
}
public function decr($name, $amount=1) {
$this->connect();
if ($amount == 1)
$this->write("DECR $name\r\n");
else
$this->write("DECRBY $name $amount\r\n");
return $this->get_response();
}
public function exists($name) {
$this->connect();
$this->write("EXISTS $name\r\n");
return $this->get_response();
}
public function delete($name) {
$this->connect();
$this->write("DEL $name\r\n");
return $this->get_response();
}
public function keys($pattern) {
$this->connect();
$this->write("KEYS $pattern\r\n");
return explode(' ', $this->get_response());
}
public function randomkey() {
$this->connect();
$this->write("RANDOMKEY\r\n");
return $this->get_response();
}
public function rename($src, $dst) {
$this->connect();
$this->write("RENAME $src $dst\r\n");
return $this->get_response();
}
public function renamenx($src, $dst) {
$this->connect();
$this->write("RENAMENX $src $dst\r\n");
return $this->get_response();
}
public function expire($name, $time) {
$this->connect();
$this->write("EXPIRE $name $time\r\n");
return $this->get_response();
}
public function push($name, $value, $tail=true) {
// default is to append the element to the list
$this->connect();
$this->write(
($tail ? 'RPUSH' : 'LPUSH') .
" $name " . strlen($value) . "\r\n$value\r\n"
);
return $this->get_response();
}
public function lpush($name, $value) {
return $this->push($name, $value, false);
}
public function rpush($name, $value) {
return $this->push($name, $value, true);
}
public function ltrim($name, $start, $end) {
$this->connect();
$this->write("LTRIM $name $start $end\r\n");
return $this->get_response();
}
public function lindex($name, $index) {
$this->connect();
$this->write("LINDEX $name $index\r\n");
return $this->get_response();
}
public function pop($name, $tail=true) {
$this->connect();
$this->write(
($tail ? 'RPOP' : 'LPOP') .
" $name\r\n"
);
return $this->get_response();
}
public function lpop($name, $value) {
return $this->pop($name, $value, false);
}
public function rpop($name, $value) {
return $this->pop($name, $value, true);
}
public function llen($name) {
$this->connect();
$this->write("LLEN $name\r\n");
return $this->get_response();
}
public function lrange($name, $start, $end) {
$this->connect();
$this->write("LRANGE $name $start $end\r\n");
return $this->get_response();
}
public function sort($name, $query=false) {
$this->connect();
$this->write($query == false ? "SORT $name\r\n" : "SORT $name $query\r\n");
return $this->get_response();
}
public function lset($name, $value, $index) {
$this->connect();
$this->write("LSET $name $index " . strlen($value) . "\r\n$value\r\n");
return $this->get_response();
}
public function sadd($name, $value) {
$this->connect();
$this->write("SADD $name " . strlen($value) . "\r\n$value\r\n");
return $this->get_response();
}
public function srem($name, $value) {
$this->connect();
$this->write("SREM $name " . strlen($value) . "\r\n$value\r\n");
return $this->get_response();
}
public function sismember($name, $value) {
$this->connect();
$this->write("SISMEMBER $name " . strlen($value) . "\r\n$value\r\n");
return $this->get_response();
}
public function sinter($sets) {
$this->connect();
$this->write('SINTER ' . implode(' ', $sets) . "\r\n");
return $this->get_response();
}
public function smembers($name) {
$this->connect();
$this->write("SMEMBERS $name\r\n");
return $this->get_response();
}
public function scard($name) {
$this->connect();
$this->write("SCARD $name\r\n");
return $this->get_response();
}
public function select_db($name) {
$this->connect();
$this->write("SELECT $name\r\n");
return $this->get_response();
}
public function move($name, $db) {
$this->connect();
$this->write("MOVE $name $db\r\n");
return $this->get_response();
}
public function save($background=false) {
$this->connect();
$this->write(($background ? "BGSAVE\r\n" : "SAVE\r\n"));
return $this->get_response();
}
public function bgsave($background=false) {
return $this->save(true);
}
public function lastsave() {
$this->connect();
$this->write("LASTSAVE\r\n");
return $this->get_response();
}
public function flushdb($all=false) {
$this->connect();
$this->write($all ? "FLUSHALL\r\n" : "FLUSHDB\r\n");
return $this->get_response();
}
public function flushall() {
return $this->flush(true);
}
public function info() {
$this->connect();
$this->write("INFO\r\n");
$info = array();
$data =& $this->get_response();
foreach (explode("\r\n", $data) as $l) {
if (!$l)
continue;
list($k, $v) = explode(':', $l, 2);
$_v = strpos($v, '.') !== false ? (float)$v : (int)$v;
$info[$k] = (string)$_v == $v ? $_v : $v;
}
return $info;
}
private function write($s) {
while ($s) {
$i = fwrite($this->_sock, $s);
if ($i == 0) // || $i == strlen($s))
break;
$s = substr($s, $i);
}
}
private function read($len=1024) {
if ($s = fgets($this->_sock))
return $s;
$this->disconnect();
trigger_error("Cannot read from socket.", E_USER_ERROR);
}
private function get_response() {
$data = trim($this->read());
$c = $data[0];
$data = substr($data, 1);
switch ($c) {
case '-':
trigger_error($data, E_USER_ERROR);
break;
case '+':
return $data;
case ':':
$i = strpos($data, '.') !== false ? (int)$data : (float)$data;
if ((string)$i != $data)
trigger_error("Cannot convert data '$c$data' to integer", E_USER_ERROR);
return $i;
case '$':
return $this->get_bulk_reply($c . $data);
case '*':
$num = (int)$data;
if ((string)$num != $data)
trigger_error("Cannot convert multi-response header '$data' to integer", E_USER_ERROR);
$result = array();
for ($i=0; $i<$num; $i++)
$result[] =& $this->get_response();
return $result;
default:
trigger_error("Invalid reply type byte: '$c'");
}
}
private function get_bulk_reply($data=null) {
if ($data === null)
$data = trim($this->read());
if ($data == '$-1')
return null;
$c = $data[0];
$data = substr($data, 1);
$bulklen = (int)$data;
if ((string)$bulklen != $data)
trigger_error("Cannot convert bulk read header '$c$data' to integer", E_USER_ERROR);
if ($c != '$')
trigger_error("Unkown response prefix for '$c$data'", E_USER_ERROR);
$buffer = '';
while ($bulklen) {
$data = fread($this->_sock,$bulklen);
$bulklen -= strlen($data);
$buffer .= $data;
}
$crlf = fread($this->_sock,2);
return $buffer;
}
}
/*
$r = new Redis();
var_dump($r->set("foo","bar"));
var_dump($r->get("foo"));
var_dump($r->info());
*/
?>
<?php
// poor man's tests
require_once('redis.php');
$r =& new Redis('localhost');
$r->connect();
$r->select_db(9);
$r->flushdb();
echo "<pre>\n";
echo $r->ping() . "\n";
echo $r->do_echo('ECHO test') . "\n";
echo "SET aaa " . $r->set('aaa', 'bbb') . "\n";
echo "SETNX aaa " . $r->set('aaa', 'ccc', true) . "\n";
echo "GET aaa " . $r->get('aaa') . "\n";
echo "INCR aaa " . $r->incr('aaa') . "\n";
echo "GET aaa " . $r->get('aaa') . "\n";
echo "INCRBY aaa 3 " . $r->incr('aaa', 2) . "\n";
echo "GET aaa " . $r->get('aaa') . "\n";
echo "DECR aaa " . $r->decr('aaa') . "\n";
echo "GET aaa " . $r->get('aaa') . "\n";
echo "DECRBY aaa 2 " . $r->decr('aaa', 2) . "\n";
echo "GET aaa " . $r->get('aaa') . "\n";
echo "EXISTS aaa " . $r->exists('aaa') . "\n";
echo "EXISTS fsfjslfjkls " . $r->exists('fsfjslfjkls') . "\n";
echo "DELETE aaa " . $r->delete('aaa') . "\n";
echo "EXISTS aaa " . $r->exists('aaa') . "\n";
echo 'SET a1 a2 a3' . $r->set('a1', 'a') . $r->set('a2', 'b') . $r->set('a3', 'c') . "\n";
echo 'KEYS a* ' . print_r($r->keys('a*'), true) . "\n";
echo 'RANDOMKEY ' . $r->randomkey('a*') . "\n";
echo 'RENAME a1 a0 ' . $r->rename('a1', 'a0') . "\n";
echo 'RENAMENX a0 a2 ' . $r->renamenx('a0', 'a2') . "\n";
echo 'RENAMENX a0 a1 ' . $r->renamenx('a0', 'a1') . "\n";
echo 'LPUSH a0 aaa ' . $r->push('a0', 'aaa') . "\n";
echo 'LPUSH a0 bbb ' . $r->push('a0', 'bbb') . "\n";
echo 'RPUSH a0 ccc ' . $r->push('a0', 'ccc', false) . "\n";
echo 'LLEN a0 ' . $r->llen('a0') . "\n";
echo 'LRANGE sdkjhfskdjfh 0 100 ' . print_r($r->lrange('sdkjhfskdjfh', 0, 100), true) . "\n";
echo 'LRANGE a0 0 0 ' . print_r($r->lrange('a0', 0, 0), true) . "\n";
echo 'LRANGE a0 0 100 ' . print_r($r->lrange('a0', 0, 100), true) . "\n";
echo 'LTRIM a0 0 1 ' . $r->ltrim('a0', 0, 1) . "\n";
echo 'LRANGE a0 0 100 ' . print_r($r->lrange('a0', 0, 100), true) . "\n";
echo 'LINDEX a0 0 ' . $r->lindex('a0', 0) . "\n";
echo 'LPUSH a0 bbb ' . $r->push('a0', 'bbb') . "\n";
echo 'LRANGE a0 0 100 ' . print_r($r->lrange('a0', 0, 100), true) . "\n";
echo 'RPOP a0 ' . $r->pop('a0') . "\n";
echo 'LPOP a0 ' . $r->pop('a0', false) . "\n";
echo 'LSET a0 ccc 0 ' . $r->lset('a0', 'ccc', 0) . "\n";
echo 'LRANGE a0 0 100 ' . print_r($r->lrange('a0', 0, 100), true) . "\n";
echo 'SADD s0 aaa ' . $r->sadd('s0', 'aaa') . "\n";
echo 'SADD s0 aaa ' . $r->sadd('s0', 'aaa') . "\n";
echo 'SADD s0 bbb ' . $r->sadd('s0', 'bbb') . "\n";
echo 'SREM s0 bbb ' . $r->srem('s0', 'bbb') . "\n";
echo 'SISMEMBER s0 aaa ' . $r->sismember('s0', 'aaa') . "\n";
echo 'SISMEMBER s0 bbb ' . $r->sismember('s0', 'bbb') . "\n";
echo 'SADD s0 bbb ' . $r->sadd('s0', 'bbb') . "\n";
echo 'SADD s1 bbb ' . $r->sadd('s1', 'bbb') . "\n";
echo 'SADD s1 aaa ' . $r->sadd('s1', 'aaa') . "\n";
echo 'SINTER s0 s1 ' . print_r($r->sinter(array('s0', 's1')), true) . "\n";
echo 'SREM s0 bbb ' . $r->srem('s0', 'bbb') . "\n";
echo 'SINTER s0 s1 ' . print_r($r->sinter(array('s0', 's1')), true) . "\n";
echo 'SMEMBERS s1 ' . print_r($r->smembers('s1'), true) . "\n";
echo 'SELECT 8 ' . $r->select_db(8) . "\n";
echo 'EXISTS s1 ' . $r->exists('s1') . "\n";
if ($r->exists('s1'))
echo 'SMEMBERS s1 ' . print_r($r->smembers('s1'), true) . "\n";
echo 'SELECT 9 ' . $r->select_db(9) . "\n";
echo 'SMEMBERS s1 ' . print_r($r->smembers('s1'), true) . "\n";
echo 'MOVE s1 8 ' . $r->move('s1', 8) . "\n";
echo 'EXISTS s1 ' . $r->exists('s1') . "\n";
if ($r->exists('s1'))
echo 'SMEMBERS s1 ' . print_r($r->smembers('s1'), true) . "\n";
echo 'SELECT 8 ' . $r->select_db(8) . "\n";
echo 'SMEMBERS s1 ' . print_r($r->smembers('s1'), true) . "\n";
echo 'SELECT 9 ' . $r->select_db(9) . "\n";
echo 'SAVE ' . $r->save() . "\n";
echo 'BGSAVE ' . $r->save(true) . "\n";
echo 'LASTSAVE ' . $r->lastsave() . "\n";
echo 'INFO ' . print_r($r->info()) . "\n";
echo "</pre>\n";
?>
#!/usr/bin/env python
""" redis.py - A client for the Redis daemon.
History:
- 20090603 fix missing errno import, add sunion and sunionstore commands,
generalize shebang (Jochen Kupperschmidt)
"""
__author__ = "Ludovico Magnocavallo <ludo\x40qix\x2eit>"
__copyright__ = "Copyright 2009, Ludovico Magnocavallo"
__license__ = "MIT"
__version__ = "0.5"
__revision__ = "$LastChangedRevision: 175 $"[22:-2]
__date__ = "$LastChangedDate: 2009-03-17 16:15:55 +0100 (Mar, 17 Mar 2009) $"[18:-2]
# TODO: Redis._get_multi_response
import socket
import decimal
import errno
BUFSIZE = 4096
class RedisError(Exception): pass
class ConnectionError(RedisError): pass
class ResponseError(RedisError): pass
class InvalidResponse(RedisError): pass
class InvalidData(RedisError): pass
class Redis(object):
"""The main Redis client.
"""
def __init__(self, host=None, port=None, timeout=None, db=None, nodelay=None, charset='utf8', errors='strict'):
self.host = host or 'localhost'
self.port = port or 6379
if timeout:
socket.setdefaulttimeout(timeout)
self.nodelay = nodelay
self.charset = charset
self.errors = errors
self._sock = None
self._fp = None
self.db = db
def _encode(self, s):
if isinstance(s, str):
return s
if isinstance(s, unicode):
try:
return s.encode(self.charset, self.errors)
except UnicodeEncodeError, e:
raise InvalidData("Error encoding unicode value '%s': %s" % (value.encode(self.charset, 'replace'), e))
return str(s)
def _write(self, s):
"""
>>> r = Redis(db=9)
>>> r.connect()
>>> r._sock.close()
>>> try:
... r._write('pippo')
... except ConnectionError, e:
... print e
Error 9 while writing to socket. Bad file descriptor.
>>>
>>>
"""
try:
self._sock.sendall(s)
except socket.error, e:
if e.args[0] == 32:
# broken pipe
self.disconnect()
raise ConnectionError("Error %s while writing to socket. %s." % tuple(e.args))
def _read(self):
try:
return self._fp.readline()
except socket.error, e:
if e.args and e.args[0] == errno.EAGAIN:
return
self.disconnect()
raise ConnectionError("Error %s while reading from socket. %s." % tuple(e.args))
if not data:
self.disconnect()
raise ConnectionError("Socket connection closed when reading.")
return data
def ping(self):
"""
>>> r = Redis(db=9)
>>> r.ping()
'PONG'
>>>
"""
self.connect()
self._write('PING\r\n')
return self.get_response()
def set(self, name, value, preserve=False, getset=False):
"""
>>> r = Redis(db=9)
>>> r.set('a', 'pippo')
'OK'
>>> r.set('a', u'pippo \u3235')
'OK'
>>> r.get('a')
u'pippo \u3235'
>>> r.set('b', 105.2)
'OK'
>>> r.set('b', 'xxx', preserve=True)
0
>>> r.get('b')
Decimal("105.2")
>>>
"""
self.connect()
# the following will raise an error for unicode values that can't be encoded to ascii
# we could probably add an 'encoding' arg to init, but then what do we do with get()?
# convert back to unicode? and what about ints, or pickled values?
if getset: command = 'GETSET'
elif preserve: command = 'SETNX'
else: command = 'SET'
value = self._encode(value)
self._write('%s %s %s\r\n%s\r\n' % (
command, name, len(value), value
))
return self.get_response()
def get(self, name):
"""
>>> r = Redis(db=9)
>>> r.set('a', 'pippo'), r.set('b', 15), r.set('c', ' \\r\\naaa\\nbbb\\r\\ncccc\\nddd\\r\\n '), r.set('d', '\\r\\n')
('OK', 'OK', 'OK', 'OK')
>>> r.get('a')
u'pippo'
>>> r.get('b')
15
>>> r.get('d')
u'\\r\\n'
>>> r.get('b')
15
>>> r.get('c')
u' \\r\\naaa\\nbbb\\r\\ncccc\\nddd\\r\\n '
>>> r.get('c')
u' \\r\\naaa\\nbbb\\r\\ncccc\\nddd\\r\\n '
>>> r.get('ajhsd')
>>>
"""
self.connect()
self._write('GET %s\r\n' % name)
return self.get_response()
def getset(self, name, value):
"""
>>> r = Redis(db=9)
>>> r.set('a', 'pippo')
'OK'
>>> r.getset('a', 2)
u'pippo'
>>>
"""
return self.set(name, value, getset=True)
def mget(self, *args):
"""
>>> r = Redis(db=9)
>>> r.set('a', 'pippo'), r.set('b', 15), r.set('c', '\\r\\naaa\\nbbb\\r\\ncccc\\nddd\\r\\n'), r.set('d', '\\r\\n')
('OK', 'OK', 'OK', 'OK')
>>> r.mget('a', 'b', 'c', 'd')
[u'pippo', 15, u'\\r\\naaa\\nbbb\\r\\ncccc\\nddd\\r\\n', u'\\r\\n']
>>>
"""
self.connect()
self._write('MGET %s\r\n' % ' '.join(args))
return self.get_response()
def incr(self, name, amount=1):
"""
>>> r = Redis(db=9)
>>> r.delete('a')
1
>>> r.incr('a')
1
>>> r.incr('a')
2
>>> r.incr('a', 2)
4
>>>
"""
self.connect()
if amount == 1:
self._write('INCR %s\r\n' % name)
else:
self._write('INCRBY %s %s\r\n' % (name, amount))
return self.get_response()
def decr(self, name, amount=1):
"""
>>> r = Redis(db=9)
>>> if r.get('a'):
... r.delete('a')
... else:
... print 1
1
>>> r.decr('a')
-1
>>> r.decr('a')
-2
>>> r.decr('a', 5)
-7
>>>
"""
self.connect()
if amount == 1:
self._write('DECR %s\r\n' % name)
else:
self._write('DECRBY %s %s\r\n' % (name, amount))
return self.get_response()
def exists(self, name):
"""
>>> r = Redis(db=9)
>>> r.exists('dsjhfksjdhfkdsjfh')
0
>>> r.set('a', 'a')
'OK'
>>> r.exists('a')
1
>>>
"""
self.connect()
self._write('EXISTS %s\r\n' % name)
return self.get_response()
def delete(self, name):
"""
>>> r = Redis(db=9)
>>> r.delete('dsjhfksjdhfkdsjfh')
0
>>> r.set('a', 'a')
'OK'
>>> r.delete('a')
1
>>> r.exists('a')
0
>>> r.delete('a')
0
>>>
"""
self.connect()
self._write('DEL %s\r\n' % name)
return self.get_response()
def get_type(self, name):
"""
>>> r = Redis(db=9)
>>> r.set('a', 3)
'OK'
>>> r.get_type('a')
'string'
>>> r.get_type('zzz')
>>>
"""
self.connect()
self._write('TYPE %s\r\n' % name)
res = self.get_response()
return None if res == 'none' else res
def keys(self, pattern):
"""
>>> r = Redis(db=9)
>>> r.flush()
'OK'
>>> r.set('a', 'a')
'OK'
>>> r.keys('a*')
[u'a']
>>> r.set('a2', 'a')
'OK'
>>> r.keys('a*')
[u'a', u'a2']
>>> r.delete('a2')
1
>>> r.keys('sjdfhskjh*')
[]
>>>
"""
self.connect()
self._write('KEYS %s\r\n' % pattern)
return self.get_response().split()
def randomkey(self):
"""
>>> r = Redis(db=9)
>>> r.set('a', 'a')
'OK'
>>> isinstance(r.randomkey(), str)
True
>>>
"""
#raise NotImplementedError("Implemented but buggy, do not use.")
self.connect()
self._write('RANDOMKEY\r\n')
return self.get_response()
def rename(self, src, dst, preserve=False):
"""
>>> r = Redis(db=9)
>>> try:
... r.rename('a', 'a')
... except ResponseError, e:
... print e
source and destination objects are the same
>>> r.rename('a', 'b')
'OK'
>>> try:
... r.rename('a', 'b')
... except ResponseError, e:
... print e
no such key
>>> r.set('a', 1)
'OK'
>>> r.rename('b', 'a', preserve=True)
0
>>>
"""
self.connect()
if preserve:
self._write('RENAMENX %s %s\r\n' % (src, dst))
return self.get_response()
else:
self._write('RENAME %s %s\r\n' % (src, dst))
return self.get_response() #.strip()
def dbsize(self):
"""
>>> r = Redis(db=9)
>>> type(r.dbsize())
<type 'int'>
>>>
"""
self.connect()
self._write('DBSIZE\r\n')
return self.get_response()
def ttl(self, name):
"""
>>> r = Redis(db=9)
>>> r.ttl('a')
-1
>>> r.expire('a', 10)
1
>>> r.ttl('a')
10
>>> r.expire('a', 0)
0
>>>
"""
self.connect()
self._write('TTL %s\r\n' % name)
return self.get_response()
def expire(self, name, time):
"""
>>> r = Redis(db=9)
>>> r.set('a', 1)
'OK'
>>> r.expire('a', 1)
1
>>> r.expire('zzzzz', 1)
0
>>>
"""
self.connect()
self._write('EXPIRE %s %s\r\n' % (name, time))
return self.get_response()
def push(self, name, value, tail=False):
"""
>>> r = Redis(db=9)
>>> r.delete('l')
1
>>> r.push('l', 'a')
'OK'
>>> r.set('a', 'a')
'OK'
>>> try:
... r.push('a', 'a')
... except ResponseError, e:
... print e
Operation against a key holding the wrong kind of value
>>>
"""
self.connect()
value = self._encode(value)
self._write('%s %s %s\r\n%s\r\n' % (
'LPUSH' if tail else 'RPUSH', name, len(value), value
))
return self.get_response()
def llen(self, name):
"""
>>> r = Redis(db=9)
>>> r.delete('l')
1
>>> r.push('l', 'a')
'OK'
>>> r.llen('l')
1
>>> r.push('l', 'a')
'OK'
>>> r.llen('l')
2
>>>
"""
self.connect()
self._write('LLEN %s\r\n' % name)
return self.get_response()
def lrange(self, name, start, end):
"""
>>> r = Redis(db=9)
>>> r.delete('l')
1
>>> r.lrange('l', 0, 1)
[]
>>> r.push('l', 'aaa')
'OK'
>>> r.lrange('l', 0, 1)
[u'aaa']
>>> r.push('l', 'bbb')
'OK'
>>> r.lrange('l', 0, 0)
[u'aaa']
>>> r.lrange('l', 0, 1)
[u'aaa', u'bbb']
>>> r.lrange('l', -1, 0)
[]
>>> r.lrange('l', -1, -1)
[u'bbb']
>>>
"""
self.connect()
self._write('LRANGE %s %s %s\r\n' % (name, start, end))
return self.get_response()
def ltrim(self, name, start, end):
"""
>>> r = Redis(db=9)
>>> r.delete('l')
1
>>> try:
... r.ltrim('l', 0, 1)
... except ResponseError, e:
... print e
no such key
>>> r.push('l', 'aaa')
'OK'
>>> r.push('l', 'bbb')
'OK'
>>> r.push('l', 'ccc')
'OK'
>>> r.ltrim('l', 0, 1)
'OK'
>>> r.llen('l')
2
>>> r.ltrim('l', 99, 95)
'OK'
>>> r.llen('l')
0
>>>
"""
self.connect()
self._write('LTRIM %s %s %s\r\n' % (name, start, end))
return self.get_response()
def lindex(self, name, index):
"""
>>> r = Redis(db=9)
>>> res = r.delete('l')
>>> r.lindex('l', 0)
>>> r.push('l', 'aaa')
'OK'
>>> r.lindex('l', 0)
u'aaa'
>>> r.lindex('l', 2)
>>> r.push('l', 'ccc')
'OK'
>>> r.lindex('l', 1)
u'ccc'
>>> r.lindex('l', -1)
u'ccc'
>>>
"""
self.connect()
self._write('LINDEX %s %s\r\n' % (name, index))
return self.get_response()
def pop(self, name, tail=False):
"""
>>> r = Redis(db=9)
>>> r.delete('l')
1
>>> r.pop('l')
>>> r.push('l', 'aaa')
'OK'
>>> r.push('l', 'bbb')
'OK'
>>> r.pop('l')
u'aaa'
>>> r.pop('l')
u'bbb'
>>> r.pop('l')
>>> r.push('l', 'aaa')
'OK'
>>> r.push('l', 'bbb')
'OK'
>>> r.pop('l', tail=True)
u'bbb'
>>> r.pop('l')
u'aaa'
>>> r.pop('l')
>>>
"""
self.connect()
self._write('%s %s\r\n' % ('RPOP' if tail else 'LPOP', name))
return self.get_response()
def lset(self, name, index, value):
"""
>>> r = Redis(db=9)
>>> r.delete('l')
1
>>> try:
... r.lset('l', 0, 'a')
... except ResponseError, e:
... print e
no such key
>>> r.push('l', 'aaa')
'OK'
>>> try:
... r.lset('l', 1, 'a')
... except ResponseError, e:
... print e
index out of range
>>> r.lset('l', 0, 'bbb')
'OK'
>>> r.lrange('l', 0, 1)
[u'bbb']
>>>
"""
self.connect()
value = self._encode(value)
self._write('LSET %s %s %s\r\n%s\r\n' % (
name, index, len(value), value
))
return self.get_response()
def lrem(self, name, value, num=0):
"""
>>> r = Redis(db=9)
>>> r.delete('l')
1
>>> r.push('l', 'aaa')
'OK'
>>> r.push('l', 'bbb')
'OK'
>>> r.push('l', 'aaa')
'OK'
>>> r.lrem('l', 'aaa')
2
>>> r.lrange('l', 0, 10)
[u'bbb']
>>> r.push('l', 'aaa')
'OK'
>>> r.push('l', 'aaa')
'OK'
>>> r.lrem('l', 'aaa', 1)
1
>>> r.lrem('l', 'aaa', 1)
1
>>> r.lrem('l', 'aaa', 1)
0
>>>
"""
self.connect()
value = self._encode(value)
self._write('LREM %s %s %s\r\n%s\r\n' % (
name, num, len(value), value
))
return self.get_response()
def sort(self, name, by=None, get=None, start=None, num=None, desc=False, alpha=False):
"""
>>> r = Redis(db=9)
>>> r.delete('l')
1
>>> r.push('l', 'ccc')
'OK'
>>> r.push('l', 'aaa')
'OK'
>>> r.push('l', 'ddd')
'OK'
>>> r.push('l', 'bbb')
'OK'
>>> r.sort('l', alpha=True)
[u'aaa', u'bbb', u'ccc', u'ddd']
>>> r.delete('l')
1
>>> for i in range(1, 5):
... res = r.push('l', 1.0 / i)
>>> r.sort('l')
[Decimal("0.25"), Decimal("0.333333333333"), Decimal("0.5"), Decimal("1.0")]
>>> r.sort('l', desc=True)
[Decimal("1.0"), Decimal("0.5"), Decimal("0.333333333333"), Decimal("0.25")]
>>> r.sort('l', desc=True, start=2, num=1)
[Decimal("0.333333333333")]
>>> r.set('weight_0.5', 10)
'OK'
>>> r.sort('l', desc=True, by='weight_*')
[Decimal("0.5"), Decimal("1.0"), Decimal("0.333333333333"), Decimal("0.25")]
>>> for i in r.sort('l', desc=True):
... res = r.set('test_%s' % i, 100 - float(i))
>>> r.sort('l', desc=True, get='test_*')
[Decimal("99.0"), Decimal("99.5"), Decimal("99.6666666667"), Decimal("99.75")]
>>> r.sort('l', desc=True, by='weight_*', get='test_*')
[Decimal("99.5"), Decimal("99.0"), Decimal("99.6666666667"), Decimal("99.75")]
>>> r.sort('l', desc=True, by='weight_*', get='missing_*')
[None, None, None, None]
>>>
"""
stmt = ['SORT', name]
if by:
stmt.append("BY %s" % by)
if start and num:
stmt.append("LIMIT %s %s" % (start, num))
if get is None:
pass
elif isinstance(get, basestring):
stmt.append("GET %s" % get)
elif isinstance(get, list) or isinstance(get, tuple):
for g in get:
stmt.append("GET %s" % g)
else:
raise RedisError("Invalid parameter 'get' for Redis sort")
if desc:
stmt.append("DESC")
if alpha:
stmt.append("ALPHA")
self.connect()
self._write(' '.join(stmt + ["\r\n"]))
return self.get_response()
def sadd(self, name, value):
"""
>>> r = Redis(db=9)
>>> res = r.delete('s')
>>> r.sadd('s', 'a')
1
>>> r.sadd('s', 'b')
1
>>>
"""
self.connect()
value = self._encode(value)
self._write('SADD %s %s\r\n%s\r\n' % (
name, len(value), value
))
return self.get_response()
def srem(self, name, value):
"""
>>> r = Redis(db=9)
>>> r.delete('s')
1
>>> r.srem('s', 'aaa')
0
>>> r.sadd('s', 'b')
1
>>> r.srem('s', 'b')
1
>>> r.sismember('s', 'b')
0
>>>
"""
self.connect()
value = self._encode(value)
self._write('SREM %s %s\r\n%s\r\n' % (
name, len(value), value
))
return self.get_response()
def sismember(self, name, value):
"""
>>> r = Redis(db=9)
>>> r.delete('s')
1
>>> r.sismember('s', 'b')
0
>>> r.sadd('s', 'a')
1
>>> r.sismember('s', 'b')
0
>>> r.sismember('s', 'a')
1
>>>
"""
self.connect()
value = self._encode(value)
self._write('SISMEMBER %s %s\r\n%s\r\n' % (
name, len(value), value
))
return self.get_response()
def sinter(self, *args):
"""
>>> r = Redis(db=9)
>>> res = r.delete('s1')
>>> res = r.delete('s2')
>>> res = r.delete('s3')
>>> r.sadd('s1', 'a')
1
>>> r.sadd('s2', 'a')
1
>>> r.sadd('s3', 'b')
1
>>> try:
... r.sinter()
... except ResponseError, e:
... print e
wrong number of arguments
>>> try:
... r.sinter('l')
... except ResponseError, e:
... print e
Operation against a key holding the wrong kind of value
>>> r.sinter('s1', 's2', 's3')
set([])
>>> r.sinter('s1', 's2')
set([u'a'])
>>>
"""
self.connect()
self._write('SINTER %s\r\n' % ' '.join(args))
return set(self.get_response())
def sinterstore(self, dest, *args):
"""
>>> r = Redis(db=9)
>>> res = r.delete('s1')
>>> res = r.delete('s2')
>>> res = r.delete('s3')
>>> r.sadd('s1', 'a')
1
>>> r.sadd('s2', 'a')
1
>>> r.sadd('s3', 'b')
1
>>> r.sinterstore('s_s', 's1', 's2', 's3')
0
>>> r.sinterstore('s_s', 's1', 's2')
1
>>> r.smembers('s_s')
set([u'a'])
>>>
"""
self.connect()
self._write('SINTERSTORE %s %s\r\n' % (dest, ' '.join(args)))
return self.get_response()
def smembers(self, name):
"""
>>> r = Redis(db=9)
>>> r.delete('s')
1
>>> r.sadd('s', 'a')
1
>>> r.sadd('s', 'b')
1
>>> try:
... r.smembers('l')
... except ResponseError, e:
... print e
Operation against a key holding the wrong kind of value
>>> r.smembers('s')
set([u'a', u'b'])
>>>
"""
self.connect()
self._write('SMEMBERS %s\r\n' % name)
return set(self.get_response())
def sunion(self, *args):
"""
>>> r = Redis(db=9)
>>> res = r.delete('s1')
>>> res = r.delete('s2')
>>> res = r.delete('s3')
>>> r.sadd('s1', 'a')
1
>>> r.sadd('s2', 'a')
1
>>> r.sadd('s3', 'b')
1
>>> r.sunion('s1', 's2', 's3')
set([u'a', u'b'])
>>> r.sadd('s2', 'c')
1
>>> r.sunion('s1', 's2', 's3')
set([u'a', u'c', u'b'])
>>>
"""
self.connect()
self._write('SUNION %s\r\n' % ' '.join(args))
return set(self.get_response())
def sunionstore(self, dest, *args):
"""
>>> r = Redis(db=9)
>>> res = r.delete('s1')
>>> res = r.delete('s2')
>>> res = r.delete('s3')
>>> r.sadd('s1', 'a')
1
>>> r.sadd('s2', 'a')
1
>>> r.sadd('s3', 'b')
1
>>> r.sunionstore('s4', 's1', 's2', 's3')
2
>>> r.smembers('s4')
set([u'a', u'b'])
>>>
"""
self.connect()
self._write('SUNIONSTORE %s %s\r\n' % (dest, ' '.join(args)))
return self.get_response()
def select(self, db):
"""
>>> r = Redis(db=9)
>>> r.delete('a')
1
>>> r.select(10)
'OK'
>>> r.set('a', 1)
'OK'
>>> r.select(9)
'OK'
>>> r.get('a')
>>>
"""
self.connect()
self._write('SELECT %s\r\n' % db)
return self.get_response()
def move(self, name, db):
"""
>>> r = Redis(db=9)
>>> r.set('a', 'a')
'OK'
>>> r.select(10)
'OK'
>>> if r.get('a'):
... r.delete('a')
... else:
... print 1
1
>>> r.select(9)
'OK'
>>> r.move('a', 10)
1
>>> r.get('a')
>>> r.select(10)
'OK'
>>> r.get('a')
u'a'
>>> r.select(9)
'OK'
>>>
"""
self.connect()
self._write('MOVE %s %s\r\n' % (name, db))
return self.get_response()
def save(self, background=False):
"""
>>> r = Redis(db=9)
>>> r.save()
'OK'
>>> try:
... resp = r.save(background=True)
... except ResponseError, e:
... assert str(e) == 'background save already in progress', str(e)
... else:
... assert resp == 'OK'
>>>
"""
self.connect()
if background:
self._write('BGSAVE\r\n')
else:
self._write('SAVE\r\n')
return self.get_response()
def lastsave(self):
"""
>>> import time
>>> r = Redis(db=9)
>>> t = int(time.time())
>>> r.save()
'OK'
>>> r.lastsave() >= t
True
>>>
"""
self.connect()
self._write('LASTSAVE\r\n')
return self.get_response()
def flush(self, all_dbs=False):
"""
>>> r = Redis(db=9)
>>> r.flush()
'OK'
>>> # r.flush(all_dbs=True)
>>>
"""
self.connect()
self._write('%s\r\n' % ('FLUSHALL' if all_dbs else 'FLUSHDB'))
return self.get_response()
def info(self):
"""
>>> r = Redis(db=9)
>>> info = r.info()
>>> info and isinstance(info, dict)
True
>>> isinstance(info.get('connected_clients'), int)
True
>>>
"""
self.connect()
self._write('INFO\r\n')
info = dict()
for l in self.get_response().split('\r\n'):
if not l:
continue
k, v = l.split(':', 1)
info[k] = int(v) if v.isdigit() else v
return info
def auth(self, passwd):
self.connect()
self._write('AUTH %s\r\n' % passwd)
return self.get_response()
def get_response(self):
data = self._read().strip()
if not data:
self.disconnect()
raise ConnectionError("Socket closed on remote end")
c = data[0]
if c == '-':
raise ResponseError(data[5:] if data[:5] == '-ERR ' else data[1:])
if c == '+':
return data[1:]
if c == '*':
try:
num = int(data[1:])
except (TypeError, ValueError):
raise InvalidResponse("Cannot convert multi-response header '%s' to integer" % data)
result = list()
for i in range(num):
result.append(self._get_value())
return result
return self._get_value(data)
def _get_value(self, data=None):
data = data or self._read().strip()
if data == '$-1':
return None
try:
c, i = data[0], (int(data[1:]) if data.find('.') == -1 else float(data[1:]))
except ValueError:
raise InvalidResponse("Cannot convert data '%s' to integer" % data)
if c == ':':
return i
if c != '$':
raise InvalidResponse("Unkown response prefix for '%s'" % data)
buf = []
while True:
data = self._read()
i -= len(data)
buf.append(data)
if i < 0:
break
data = ''.join(buf)[:-2]
try:
return int(data) if data.find('.') == -1 else decimal.Decimal(data)
except (ValueError, decimal.InvalidOperation):
return data.decode(self.charset)
def disconnect(self):
if isinstance(self._sock, socket.socket):
try:
self._sock.close()
except socket.error:
pass
self._sock = None
self._fp = None
def connect(self):
"""
>>> r = Redis(db=9)
>>> r.connect()
>>> isinstance(r._sock, socket.socket)
True
>>> r.disconnect()
>>>
"""
if isinstance(self._sock, socket.socket):
return
try:
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect((self.host, self.port))
except socket.error, e:
raise ConnectionError("Error %s connecting to %s:%s. %s." % (e.args[0], self.host, self.port, e.args[1]))
else:
self._sock = sock
self._fp = self._sock.makefile('r')
if self.db:
self.select(self.db)
if self.nodelay is not None:
self._sock.setsockopt(socket.SOL_TCP, socket.TCP_NODELAY, self.nodelay)
if __name__ == '__main__':
import doctest
doctest.testmod()
nohup.out
redis/*
rdsrv
pkg/*
coverage/*
.idea
Copyright (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.
\ No newline at end of file
# redis-rb
A ruby client library for the redis key value storage system.
## Information about redis
Redis is a key value store with some interesting features:
1. It's fast.
2. Keys are strings but values can have types of "NONE", "STRING", "LIST", or "SET". List's can be atomically push'd, pop'd, lpush'd, lpop'd and indexed. This allows you to store things like lists of comments under one key while retaining the ability to append comments without reading and putting back the whole list.
See [redis on code.google.com](http://code.google.com/p/redis/wiki/README) for more information.
## Dependencies
1. rspec -
sudo gem install rspec
2. redis -
rake redis:install
2. dtach -
rake dtach:install
3. git - git is the new black.
## Setup
Use the tasks mentioned above (in Dependencies) to get your machine setup.
## Examples
Check the examples/ directory. *Note* you need to have redis-server running first.
\ No newline at end of file
require 'rubygems'
require 'rake/gempackagetask'
require 'rubygems/specification'
require 'date'
require 'spec/rake/spectask'
require 'tasks/redis.tasks'
GEM = 'redis'
GEM_NAME = 'redis'
GEM_VERSION = '0.1'
AUTHORS = ['Ezra Zygmuntowicz', 'Taylor Weibley', 'Matthew Clark', 'Brian McKinney', 'Salvatore Sanfilippo', 'Luca Guidi']
EMAIL = "ez@engineyard.com"
HOMEPAGE = "http://github.com/ezmobius/redis-rb"
SUMMARY = "Ruby client library for redis key value storage server"
spec = Gem::Specification.new do |s|
s.name = GEM
s.version = GEM_VERSION
s.platform = Gem::Platform::RUBY
s.has_rdoc = true
s.extra_rdoc_files = ["LICENSE"]
s.summary = SUMMARY
s.description = s.summary
s.authors = AUTHORS
s.email = EMAIL
s.homepage = HOMEPAGE
s.add_dependency "rspec"
s.require_path = 'lib'
s.autorequire = GEM
s.files = %w(LICENSE README.markdown Rakefile) + Dir.glob("{lib,tasks,spec}/**/*")
end
task :default => :spec
desc "Run specs"
Spec::Rake::SpecTask.new do |t|
t.spec_files = FileList['spec/**/*_spec.rb']
t.spec_opts = %w(-fs --color)
end
Rake::GemPackageTask.new(spec) do |pkg|
pkg.gem_spec = spec
end
desc "install the gem locally"
task :install => [:package] do
sh %{sudo gem install pkg/#{GEM}-#{GEM_VERSION}}
end
desc "create a gemspec file"
task :make_spec do
File.open("#{GEM}.gemspec", "w") do |file|
file.puts spec.to_ruby
end
end
desc "Run all examples with RCov"
Spec::Rake::SpecTask.new(:rcov) do |t|
t.spec_files = FileList['spec/**/*_spec.rb']
t.rcov = true
end
require 'benchmark'
$:.push File.join(File.dirname(__FILE__), 'lib')
require 'redis'
times = 20000
@r = Redis.new#(:debug => true)
@r['foo'] = "The first line we sent to the server is some text"
Benchmark.bmbm do |x|
x.report("set") do
20000.times do |i|
@r["set#{i}"] = "The first line we sent to the server is some text"; @r["foo#{i}"]
end
end
x.report("set (pipelined)") do
@r.pipelined do |pipeline|
20000.times do |i|
pipeline["set_pipelined#{i}"] = "The first line we sent to the server is some text"; @r["foo#{i}"]
end
end
end
x.report("push+trim") do
20000.times do |i|
@r.push_head "push_trim#{i}", i
@r.list_trim "push_trim#{i}", 0, 30
end
end
x.report("push+trim (pipelined)") do
@r.pipelined do |pipeline|
20000.times do |i|
pipeline.push_head "push_trim_pipelined#{i}", i
pipeline.list_trim "push_trim_pipelined#{i}", 0, 30
end
end
end
end
@r.keys('*').each do |k|
@r.delete k
end
\ No newline at end of file
require '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
\ No newline at end of file
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