New file dump format, perl client library added

This commit is contained in:
antirez 2009-03-25 16:47:22 +01:00
parent 7b45bfb2a4
commit f78fd11b71
17 changed files with 1129 additions and 104 deletions

3
TODO
View File

@ -7,3 +7,6 @@
- maxclients directive - maxclients directive
- check 'server.dirty' everywere - check 'server.dirty' everywere
- replication automated tests - replication automated tests
- a command, or an external tool, to perform the MD5SUM of the whole dataset, so that if the dataset between two servers is identical, so will be the MD5SUM
* Include Lua and Perl bindings

View File

@ -9,9 +9,9 @@ code or recent bugfixes read more.
How to get the lastest versions of client libraries source code How to get the lastest versions of client libraries source code
--------------------------------------------------------------- ---------------------------------------------------------------
Note that while the PHP and Python versions are the most uptodate available Note that while the pure PHP, Tcl and Python libraries are the most uptodate
libraries, the Ruby and Erlang libraries have their own sites so you may want available libraries, all the other libraries have their own repositories where
to grab this libraries from their main sites: it's possible to grab the most recent version:
Ruby lib source code: Ruby lib source code:
http://github.com/ezmobius/redis-rb/tree/master http://github.com/ezmobius/redis-rb/tree/master
@ -19,10 +19,11 @@ http://github.com/ezmobius/redis-rb/tree/master
Erlang lib source code: Erlang lib source code:
http://bitbucket.org/adroll/erldis/ http://bitbucket.org/adroll/erldis/
For the languages with development code in the Redis SVN, check this urls for unstable versions of the libs: Perl lib source code:
(web) http://svn.rot13.org/index.cgi/Redis
(svn) svn://svn.rot13.org/Redis/
Python lib source code: Redis-php PHP C module:
http://code.google.com/p/redis/source/browse/#svn/trunk/client-libraries/python http://code.google.com/p/phpredis/
PHP lib source code: For all the rest check the Redis tarball or Git repository.
http://code.google.com/p/redis/source/browse/#svn/trunk/client-libraries/php

View File

@ -0,0 +1,8 @@
Revision history for Redis
0.01 Sun Mar 22 19:02:17 CET 2009
First version, tracking git://github.com/antirez/redis
0.08 Tue Mar 24 22:38:59 CET 2009
This version supports new protocol introduced in beta 8
Version bump to be in-sync with Redis version

View File

@ -0,0 +1,8 @@
Changes
MANIFEST
Makefile.PL
README
lib/Redis.pm
t/00-load.t
t/pod-coverage.t
t/pod.t

View File

@ -0,0 +1,19 @@
use strict;
use warnings;
use ExtUtils::MakeMaker;
WriteMakefile(
NAME => 'Redis',
AUTHOR => 'Dobrica Pavlinusic <dpavlin@rot13.org>',
VERSION_FROM => 'lib/Redis.pm',
ABSTRACT_FROM => 'lib/Redis.pm',
PL_FILES => {},
PREREQ_PM => {
'Test::More' => 0,
'IO::Socket::INET' => 0,
'Data::Dump' => 0,
'Carp' => 0,
},
dist => { COMPRESS => 'gzip -9f', SUFFIX => 'gz', },
clean => { FILES => 'Redis-*' },
);

View File

@ -0,0 +1,43 @@
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.

View File

@ -0,0 +1,422 @@
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

View File

@ -0,0 +1,70 @@
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;

View File

@ -0,0 +1,85 @@
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;

View File

@ -0,0 +1,24 @@
#!/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' ) },
});

View File

@ -0,0 +1,9 @@
#!perl -T
use Test::More tests => 1;
BEGIN {
use_ok( 'Redis' );
}
diag( "Testing Redis $Redis::VERSION, Perl $], $^X" );

View File

@ -0,0 +1,189 @@
#!/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' );

View File

@ -0,0 +1,30 @@
#!/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 );

View File

@ -0,0 +1,30 @@
#!/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 );

View File

@ -0,0 +1,18 @@
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();

View File

@ -0,0 +1,12 @@
#!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();

246
redis.c
View File

@ -46,6 +46,7 @@
#include <fcntl.h> #include <fcntl.h>
#include <sys/time.h> #include <sys/time.h>
#include <sys/resource.h> #include <sys/resource.h>
#include <limits.h>
#include "ae.h" /* Event driven programming library */ #include "ae.h" /* Event driven programming library */
#include "sds.h" /* Dynamic safe strings */ #include "sds.h" /* Dynamic safe strings */
@ -82,9 +83,28 @@
#define REDIS_LIST 1 #define REDIS_LIST 1
#define REDIS_SET 2 #define REDIS_SET 2
#define REDIS_HASH 3 #define REDIS_HASH 3
/* Object types only used for dumping to disk */
#define REDIS_SELECTDB 254 #define REDIS_SELECTDB 254
#define REDIS_EOF 255 #define REDIS_EOF 255
/* Defines related to the dump file format. To store 32 bits lengths for short
* keys requires a lot of space, so we check the most significant 2 bits of
* the first byte to interpreter the length:
*
* 00|000000 => if the two MSB are 00 the len is the 6 bits of this byte
* 01|000000 00000000 => 01, the len is 14 byes, 6 bits + 8 bits of next byte
* 10|000000 [32 bit integer] => if it's 01, a full 32 bit len will follow
* 11|000000 [64 bit integer] => if it's 11, a full 64 bit len will follow
*
* 64 bit lengths are not used currently. Lenghts up to 63 are stored using
* a single byte, most DB keys, and may values, will fit inside. */
#define REDIS_RDB_6BITLEN 0
#define REDIS_RDB_14BITLEN 1
#define REDIS_RDB_32BITLEN 2
#define REDIS_RDB_64BITLEN 3
#define REDIS_RDB_LENERR UINT_MAX
/* Client flags */ /* Client flags */
#define REDIS_CLOSE 1 /* This client connection should be closed ASAP */ #define REDIS_CLOSE 1 /* This client connection should be closed ASAP */
#define REDIS_SLAVE 2 /* This client is a slave server */ #define REDIS_SLAVE 2 /* This client is a slave server */
@ -230,11 +250,11 @@ static void freeSetObject(robj *o);
static void decrRefCount(void *o); static void decrRefCount(void *o);
static robj *createObject(int type, void *ptr); static robj *createObject(int type, void *ptr);
static void freeClient(redisClient *c); static void freeClient(redisClient *c);
static int loadDb(char *filename); static int rdbLoad(char *filename);
static void addReply(redisClient *c, robj *obj); static void addReply(redisClient *c, robj *obj);
static void addReplySds(redisClient *c, sds s); static void addReplySds(redisClient *c, sds s);
static void incrRefCount(robj *o); static void incrRefCount(robj *o);
static int saveDbBackground(char *filename); static int rdbSaveBackground(char *filename);
static robj *createStringObject(char *ptr, size_t len); static robj *createStringObject(char *ptr, size_t len);
static void replicationFeedSlaves(list *slaves, struct redisCommand *cmd, int dictid, robj **argv, int argc); static void replicationFeedSlaves(list *slaves, struct redisCommand *cmd, int dictid, robj **argv, int argc);
static int syncWithMaster(void); static int syncWithMaster(void);
@ -641,7 +661,7 @@ int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
now-server.lastsave > sp->seconds) { now-server.lastsave > sp->seconds) {
redisLog(REDIS_NOTICE,"%d changes in %d seconds. Saving...", redisLog(REDIS_NOTICE,"%d changes in %d seconds. Saving...",
sp->changes, sp->seconds); sp->changes, sp->seconds);
saveDbBackground(server.dbfilename); rdbSaveBackground(server.dbfilename);
break; break;
} }
} }
@ -1394,12 +1414,37 @@ static void decrRefCount(void *obj) {
/*============================ DB saving/loading ============================ */ /*============================ DB saving/loading ============================ */
static int rdbSaveType(FILE *fp, unsigned char type) {
if (fwrite(&type,1,1,fp) == 0) return -1;
return 0;
}
static int rdbSaveLen(FILE *fp, uint32_t len) {
unsigned char buf[2];
if (len < (1<<6)) {
/* Save a 6 bit len */
buf[0] = (len&0xFF)|REDIS_RDB_6BITLEN;
if (fwrite(buf,1,1,fp) == 0) return -1;
} else if (len < (1<<14)) {
/* Save a 14 bit len */
buf[0] = ((len>>8)&0xFF)|REDIS_RDB_14BITLEN;
buf[1] = len&0xFF;
if (fwrite(buf,4,1,fp) == 0) return -1;
} else {
/* Save a 32 bit len */
buf[0] = REDIS_RDB_32BITLEN;
if (fwrite(buf,1,1,fp) == 0) return -1;
len = htonl(len);
if (fwrite(&len,4,1,fp) == 0) return -1;
}
return 0;
}
/* Save the DB on disk. Return REDIS_ERR on error, REDIS_OK on success */ /* Save the DB on disk. Return REDIS_ERR on error, REDIS_OK on success */
static int saveDb(char *filename) { static int rdbSave(char *filename) {
dictIterator *di = NULL; dictIterator *di = NULL;
dictEntry *de; dictEntry *de;
uint32_t len;
uint8_t type;
FILE *fp; FILE *fp;
char tmpfile[256]; char tmpfile[256];
int j; int j;
@ -1410,7 +1455,7 @@ static int saveDb(char *filename) {
redisLog(REDIS_WARNING, "Failed saving the DB: %s", strerror(errno)); redisLog(REDIS_WARNING, "Failed saving the DB: %s", strerror(errno));
return REDIS_ERR; return REDIS_ERR;
} }
if (fwrite("REDIS0000",9,1,fp) == 0) goto werr; if (fwrite("REDIS0001",9,1,fp) == 0) goto werr;
for (j = 0; j < server.dbnum; j++) { for (j = 0; j < server.dbnum; j++) {
dict *d = server.dict[j]; dict *d = server.dict[j];
if (dictGetHashTableUsed(d) == 0) continue; if (dictGetHashTableUsed(d) == 0) continue;
@ -1421,59 +1466,54 @@ static int saveDb(char *filename) {
} }
/* Write the SELECT DB opcode */ /* Write the SELECT DB opcode */
type = REDIS_SELECTDB; if (rdbSaveType(fp,REDIS_SELECTDB) == -1) goto werr;
len = htonl(j); if (rdbSaveLen(fp,j) == -1) goto werr;
if (fwrite(&type,1,1,fp) == 0) goto werr;
if (fwrite(&len,4,1,fp) == 0) goto werr;
/* Iterate this DB writing every entry */ /* Iterate this DB writing every entry */
while((de = dictNext(di)) != NULL) { while((de = dictNext(di)) != NULL) {
robj *key = dictGetEntryKey(de); robj *key = dictGetEntryKey(de);
robj *o = dictGetEntryVal(de); robj *o = dictGetEntryVal(de);
type = o->type; if (rdbSaveType(fp,o->type) == -1) goto werr;
len = htonl(sdslen(key->ptr)); if (rdbSaveLen(fp,sdslen(key->ptr)) == -1) goto werr;
if (fwrite(&type,1,1,fp) == 0) goto werr;
if (fwrite(&len,4,1,fp) == 0) goto werr;
if (fwrite(key->ptr,sdslen(key->ptr),1,fp) == 0) goto werr; if (fwrite(key->ptr,sdslen(key->ptr),1,fp) == 0) goto werr;
if (type == REDIS_STRING) { if (o->type == REDIS_STRING) {
/* Save a string value */ /* Save a string value */
sds sval = o->ptr; sds sval = o->ptr;
len = htonl(sdslen(sval));
if (fwrite(&len,4,1,fp) == 0) goto werr; if (rdbSaveLen(fp,sdslen(sval)) == -1) goto werr;
if (sdslen(sval) && if (sdslen(sval) &&
fwrite(sval,sdslen(sval),1,fp) == 0) goto werr; fwrite(sval,sdslen(sval),1,fp) == 0) goto werr;
} else if (type == REDIS_LIST) { } else if (o->type == REDIS_LIST) {
/* Save a list value */ /* Save a list value */
list *list = o->ptr; list *list = o->ptr;
listNode *ln = list->head; listNode *ln = list->head;
len = htonl(listLength(list)); if (rdbSaveLen(fp,listLength(list)) == -1) goto werr;
if (fwrite(&len,4,1,fp) == 0) goto werr;
while(ln) { while(ln) {
robj *eleobj = listNodeValue(ln); robj *eleobj = listNodeValue(ln);
len = htonl(sdslen(eleobj->ptr));
if (fwrite(&len,4,1,fp) == 0) goto werr; if (rdbSaveLen(fp,sdslen(eleobj->ptr)) == -1) goto werr;
if (sdslen(eleobj->ptr) && fwrite(eleobj->ptr,sdslen(eleobj->ptr),1,fp) == 0) if (sdslen(eleobj->ptr) &&
fwrite(eleobj->ptr,sdslen(eleobj->ptr),1,fp) == 0)
goto werr; goto werr;
ln = ln->next; ln = ln->next;
} }
} else if (type == REDIS_SET) { } else if (o->type == REDIS_SET) {
/* Save a set value */ /* Save a set value */
dict *set = o->ptr; dict *set = o->ptr;
dictIterator *di = dictGetIterator(set); dictIterator *di = dictGetIterator(set);
dictEntry *de; dictEntry *de;
if (!set) oom("dictGetIteraotr"); if (!set) oom("dictGetIteraotr");
len = htonl(dictGetHashTableUsed(set)); if (rdbSaveLen(fp,dictGetHashTableUsed(set)) == -1) goto werr;
if (fwrite(&len,4,1,fp) == 0) goto werr;
while((de = dictNext(di)) != NULL) { while((de = dictNext(di)) != NULL) {
robj *eleobj; robj *eleobj;
eleobj = dictGetEntryKey(de); eleobj = dictGetEntryKey(de);
len = htonl(sdslen(eleobj->ptr)); if (rdbSaveLen(fp,sdslen(eleobj->ptr)) == -1) goto werr;
if (fwrite(&len,4,1,fp) == 0) goto werr; if (sdslen(eleobj->ptr) &&
if (sdslen(eleobj->ptr) && fwrite(eleobj->ptr,sdslen(eleobj->ptr),1,fp) == 0) fwrite(eleobj->ptr,sdslen(eleobj->ptr),1,fp) == 0)
goto werr; goto werr;
} }
dictReleaseIterator(di); dictReleaseIterator(di);
@ -1484,8 +1524,9 @@ static int saveDb(char *filename) {
dictReleaseIterator(di); dictReleaseIterator(di);
} }
/* EOF opcode */ /* EOF opcode */
type = REDIS_EOF; if (rdbSaveType(fp,REDIS_EOF) == -1) goto werr;
if (fwrite(&type,1,1,fp) == 0) goto werr;
/* Make sure data will not remain on the OS's output buffers */
fflush(fp); fflush(fp);
fsync(fileno(fp)); fsync(fileno(fp));
fclose(fp); fclose(fp);
@ -1510,14 +1551,14 @@ werr:
return REDIS_ERR; return REDIS_ERR;
} }
static int saveDbBackground(char *filename) { static int rdbSaveBackground(char *filename) {
pid_t childpid; pid_t childpid;
if (server.bgsaveinprogress) return REDIS_ERR; if (server.bgsaveinprogress) return REDIS_ERR;
if ((childpid = fork()) == 0) { if ((childpid = fork()) == 0) {
/* Child */ /* Child */
close(server.fd); close(server.fd);
if (saveDb(filename) == REDIS_OK) { if (rdbSave(filename) == REDIS_OK) {
exit(0); exit(0);
} else { } else {
exit(1); exit(1);
@ -1531,90 +1572,109 @@ static int saveDbBackground(char *filename) {
return REDIS_OK; /* unreached */ return REDIS_OK; /* unreached */
} }
static int loadType(FILE *fp) { static int rdbLoadType(FILE *fp) {
uint8_t type; unsigned char type;
if (fread(&type,1,1,fp) == 0) return -1; if (fread(&type,1,1,fp) == 0) return -1;
return type; return type;
} }
static int loadDb(char *filename) { static uint32_t rdbLoadLen(FILE *fp, int rdbver) {
unsigned char buf[2];
uint32_t len;
if (rdbver == 0) {
if (fread(&len,4,1,fp) == 0) return REDIS_RDB_LENERR;
return ntohl(len);
} else {
if (fread(buf,1,1,fp) == 0) return REDIS_RDB_LENERR;
if ((buf[0]&0xC0) == REDIS_RDB_6BITLEN) {
/* Read a 6 bit len */
return buf[0];
} else if ((buf[0]&0xC0) == REDIS_RDB_14BITLEN) {
/* Read a 14 bit len */
if (fread(buf+1,1,1,fp) == 0) return REDIS_RDB_LENERR;
return ((buf[0]&0x3F)<<8)|buf[1];
} else {
/* Read a 32 bit len */
if (fread(&len,4,1,fp) == 0) return REDIS_RDB_LENERR;
return ntohl(len);
}
}
return 0;
}
static robj *rdbLoadStringObject(FILE*fp,int rdbver) {
uint32_t len = rdbLoadLen(fp,rdbver);
sds val;
if (len == REDIS_RDB_LENERR) return NULL;
val = sdsnewlen(NULL,len);
if (len && fread(val,len,1,fp) == 0) {
sdsfree(val);
return NULL;
}
return createObject(REDIS_STRING,val);
}
static int rdbLoad(char *filename) {
FILE *fp; FILE *fp;
char buf[REDIS_LOADBUF_LEN]; /* Try to use this buffer instead of */ robj *keyobj = NULL;
char vbuf[REDIS_LOADBUF_LEN]; /* malloc() when the element is small */ uint32_t dbid;
char *key = NULL, *val = NULL;
uint32_t klen,vlen,dbid;
int type; int type;
int retval; int retval;
dict *d = server.dict[0]; dict *d = server.dict[0];
char buf[1024];
int rdbver;
fp = fopen(filename,"r"); fp = fopen(filename,"r");
if (!fp) return REDIS_ERR; if (!fp) return REDIS_ERR;
if (fread(buf,9,1,fp) == 0) goto eoferr; if (fread(buf,9,1,fp) == 0) goto eoferr;
if (memcmp(buf,"REDIS0000",9) != 0) { buf[9] = '\0';
if (memcmp(buf,"REDIS",5) != 0) {
fclose(fp); fclose(fp);
redisLog(REDIS_WARNING,"Wrong signature trying to load DB from file"); redisLog(REDIS_WARNING,"Wrong signature trying to load DB from file");
return REDIS_ERR; return REDIS_ERR;
} }
rdbver = atoi(buf+5);
if (rdbver > 1) {
fclose(fp);
redisLog(REDIS_WARNING,"Can't handle RDB format version %d",rdbver);
return REDIS_ERR;
}
while(1) { while(1) {
robj *o; robj *o;
/* Read type. */ /* Read type. */
if ((type = loadType(fp)) == -1) goto eoferr; if ((type = rdbLoadType(fp)) == -1) goto eoferr;
if (type == REDIS_EOF) break; if (type == REDIS_EOF) break;
/* Handle SELECT DB opcode as a special case */ /* Handle SELECT DB opcode as a special case */
if (type == REDIS_SELECTDB) { if (type == REDIS_SELECTDB) {
if (fread(&dbid,4,1,fp) == 0) goto eoferr; if ((dbid = rdbLoadLen(fp,rdbver)) == REDIS_RDB_LENERR) goto eoferr;
dbid = ntohl(dbid);
if (dbid >= (unsigned)server.dbnum) { if (dbid >= (unsigned)server.dbnum) {
redisLog(REDIS_WARNING,"FATAL: Data file was created with a Redis server compiled to handle more than %d databases. Exiting\n", server.dbnum); redisLog(REDIS_WARNING,"FATAL: Data file was created with a Redis server configured to handle more than %d databases. Exiting\n", server.dbnum);
exit(1); exit(1);
} }
d = server.dict[dbid]; d = server.dict[dbid];
continue; continue;
} }
/* Read key */ /* Read key */
if (fread(&klen,4,1,fp) == 0) goto eoferr; if ((keyobj = rdbLoadStringObject(fp,rdbver)) == NULL) goto eoferr;
klen = ntohl(klen);
if (klen <= REDIS_LOADBUF_LEN) {
key = buf;
} else {
key = zmalloc(klen);
if (!key) oom("Loading DB from file");
}
if (fread(key,klen,1,fp) == 0) goto eoferr;
if (type == REDIS_STRING) { if (type == REDIS_STRING) {
/* Read string value */ /* Read string value */
if (fread(&vlen,4,1,fp) == 0) goto eoferr; if ((o = rdbLoadStringObject(fp,rdbver)) == NULL) goto eoferr;
vlen = ntohl(vlen);
if (vlen <= REDIS_LOADBUF_LEN) {
val = vbuf;
} else {
val = zmalloc(vlen);
if (!val) oom("Loading DB from file");
}
if (vlen && fread(val,vlen,1,fp) == 0) goto eoferr;
o = createObject(REDIS_STRING,sdsnewlen(val,vlen));
} else if (type == REDIS_LIST || type == REDIS_SET) { } else if (type == REDIS_LIST || type == REDIS_SET) {
/* Read list/set value */ /* Read list/set value */
uint32_t listlen; uint32_t listlen;
if (fread(&listlen,4,1,fp) == 0) goto eoferr;
listlen = ntohl(listlen); if ((listlen = rdbLoadLen(fp,rdbver)) == REDIS_RDB_LENERR)
goto eoferr;
o = (type == REDIS_LIST) ? createListObject() : createSetObject(); o = (type == REDIS_LIST) ? createListObject() : createSetObject();
/* Load every single element of the list/set */ /* Load every single element of the list/set */
while(listlen--) { while(listlen--) {
robj *ele; robj *ele;
if (fread(&vlen,4,1,fp) == 0) goto eoferr; if ((ele = rdbLoadStringObject(fp,rdbver)) == NULL) goto eoferr;
vlen = ntohl(vlen);
if (vlen <= REDIS_LOADBUF_LEN) {
val = vbuf;
} else {
val = zmalloc(vlen);
if (!val) oom("Loading DB from file");
}
if (vlen && fread(val,vlen,1,fp) == 0) goto eoferr;
ele = createObject(REDIS_STRING,sdsnewlen(val,vlen));
if (type == REDIS_LIST) { if (type == REDIS_LIST) {
if (!listAddNodeTail((list*)o->ptr,ele)) if (!listAddNodeTail((list*)o->ptr,ele))
oom("listAddNodeTail"); oom("listAddNodeTail");
@ -1622,30 +1682,23 @@ static int loadDb(char *filename) {
if (dictAdd((dict*)o->ptr,ele,NULL) == DICT_ERR) if (dictAdd((dict*)o->ptr,ele,NULL) == DICT_ERR)
oom("dictAdd"); oom("dictAdd");
} }
/* free the temp buffer if needed */
if (val != vbuf) zfree(val);
val = NULL;
} }
} else { } else {
assert(0 != 0); assert(0 != 0);
} }
/* Add the new object in the hash table */ /* Add the new object in the hash table */
retval = dictAdd(d,createStringObject(key,klen),o); retval = dictAdd(d,keyobj,o);
if (retval == DICT_ERR) { if (retval == DICT_ERR) {
redisLog(REDIS_WARNING,"Loading DB, duplicated key found! Unrecoverable error, exiting now."); redisLog(REDIS_WARNING,"Loading DB, duplicated key (%s) found! Unrecoverable error, exiting now.", keyobj->ptr);
exit(1); exit(1);
} }
/* Iteration cleanup */ keyobj = o = NULL;
if (key != buf) zfree(key);
if (val != vbuf) zfree(val);
key = val = NULL;
} }
fclose(fp); fclose(fp);
return REDIS_OK; return REDIS_OK;
eoferr: /* unexpected end of file is handled here with a fatal exit */ eoferr: /* unexpected end of file is handled here with a fatal exit */
if (key != buf) zfree(key); decrRefCount(keyobj);
if (val != vbuf) zfree(val);
redisLog(REDIS_WARNING,"Short read loading DB. Unrecoverable error, exiting now."); redisLog(REDIS_WARNING,"Short read loading DB. Unrecoverable error, exiting now.");
exit(1); exit(1);
return REDIS_ERR; /* Just to avoid warning */ return REDIS_ERR; /* Just to avoid warning */
@ -1894,7 +1947,7 @@ static void typeCommand(redisClient *c) {
} }
static void saveCommand(redisClient *c) { static void saveCommand(redisClient *c) {
if (saveDb(server.dbfilename) == REDIS_OK) { if (rdbSave(server.dbfilename) == REDIS_OK) {
addReply(c,shared.ok); addReply(c,shared.ok);
} else { } else {
addReply(c,shared.err); addReply(c,shared.err);
@ -1906,7 +1959,7 @@ static void bgsaveCommand(redisClient *c) {
addReplySds(c,sdsnew("-ERR background save already in progress\r\n")); addReplySds(c,sdsnew("-ERR background save already in progress\r\n"));
return; return;
} }
if (saveDbBackground(server.dbfilename) == REDIS_OK) { if (rdbSaveBackground(server.dbfilename) == REDIS_OK) {
addReply(c,shared.ok); addReply(c,shared.ok);
} else { } else {
addReply(c,shared.err); addReply(c,shared.err);
@ -1915,7 +1968,7 @@ static void bgsaveCommand(redisClient *c) {
static void shutdownCommand(redisClient *c) { static void shutdownCommand(redisClient *c) {
redisLog(REDIS_WARNING,"User requested shutdown, saving DB..."); redisLog(REDIS_WARNING,"User requested shutdown, saving DB...");
if (saveDb(server.dbfilename) == REDIS_OK) { if (rdbSave(server.dbfilename) == REDIS_OK) {
if (server.daemonize) { if (server.daemonize) {
unlink(server.pidfile); unlink(server.pidfile);
} }
@ -2508,13 +2561,13 @@ static void sinterstoreCommand(redisClient *c) {
static void flushdbCommand(redisClient *c) { static void flushdbCommand(redisClient *c) {
dictEmpty(c->dict); dictEmpty(c->dict);
addReply(c,shared.ok); addReply(c,shared.ok);
saveDb(server.dbfilename); rdbSave(server.dbfilename);
} }
static void flushallCommand(redisClient *c) { static void flushallCommand(redisClient *c) {
emptyDb(); emptyDb();
addReply(c,shared.ok); addReply(c,shared.ok);
saveDb(server.dbfilename); rdbSave(server.dbfilename);
} }
redisSortOperation *createSortOperation(int type, robj *pattern) { redisSortOperation *createSortOperation(int type, robj *pattern) {
@ -2923,7 +2976,8 @@ static void syncCommand(redisClient *c) {
if (c->flags & REDIS_SLAVE) return; if (c->flags & REDIS_SLAVE) return;
redisLog(REDIS_NOTICE,"Slave ask for syncronization"); redisLog(REDIS_NOTICE,"Slave ask for syncronization");
if (flushClientOutput(c) == REDIS_ERR || saveDb(server.dbfilename) != REDIS_OK) if (flushClientOutput(c) == REDIS_ERR ||
rdbSave(server.dbfilename) != REDIS_OK)
goto closeconn; goto closeconn;
fd = open(server.dbfilename, O_RDONLY); fd = open(server.dbfilename, O_RDONLY);
@ -3020,7 +3074,7 @@ static int syncWithMaster(void) {
return REDIS_ERR; return REDIS_ERR;
} }
emptyDb(); emptyDb();
if (loadDb(server.dbfilename) != REDIS_OK) { if (rdbLoad(server.dbfilename) != REDIS_OK) {
redisLog(REDIS_WARNING,"Failed trying to load the MASTER synchronization DB from disk"); redisLog(REDIS_WARNING,"Failed trying to load the MASTER synchronization DB from disk");
close(fd); close(fd);
return REDIS_ERR; return REDIS_ERR;
@ -3079,7 +3133,7 @@ int main(int argc, char **argv) {
initServer(); initServer();
if (server.daemonize) daemonize(); if (server.daemonize) daemonize();
redisLog(REDIS_NOTICE,"Server started, Redis version " REDIS_VERSION); redisLog(REDIS_NOTICE,"Server started, Redis version " REDIS_VERSION);
if (loadDb(server.dbfilename) == REDIS_OK) if (rdbLoad(server.dbfilename) == REDIS_OK)
redisLog(REDIS_NOTICE,"DB loaded from disk"); redisLog(REDIS_NOTICE,"DB loaded from disk");
if (aeCreateFileEvent(server.el, server.fd, AE_READABLE, if (aeCreateFileEvent(server.el, server.fd, AE_READABLE,
acceptHandler, NULL, NULL) == AE_ERR) oom("creating file event"); acceptHandler, NULL, NULL) == AE_ERR) oom("creating file event");