Compare commits
21 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ab648d973d | ||
|
|
5ab72bedb7 | ||
|
|
9a83d13fb5 | ||
|
|
6662806a26 | ||
|
|
6e3f1a9e47 | ||
|
|
d12f26c231 | ||
|
|
a502f48320 | ||
|
|
0c71939c05 | ||
|
|
a4f46f211c | ||
|
|
8185c2e9f1 | ||
|
|
71bb31bf16 | ||
|
|
c7d23f3ed1 | ||
|
|
09cefcfbb3 | ||
|
|
1ca8deebef | ||
|
|
b7f667b553 | ||
|
|
067ad37f96 | ||
|
|
a6e312bf9a | ||
|
|
0e841b998a | ||
|
|
cd0458470e | ||
|
|
678dbc48fd | ||
|
|
3a91aca033 |
@@ -18,6 +18,29 @@ to modify your program in order to use Redis 2.4.
|
||||
CHANGELOG
|
||||
---------
|
||||
|
||||
What's new in Redis 2.4.1
|
||||
=========================
|
||||
|
||||
* [BUGFIX] FLUSHALL was not replicated nor written into the Append Only File.
|
||||
* [BUGFIX] FLUSHALL now only performs a sync SAVE if there is at least
|
||||
one save point configured.
|
||||
|
||||
What's new in Redis 2.4.0
|
||||
=========================
|
||||
|
||||
* [BUGFIX] redis-cli segfault with single numerical argument fixed.
|
||||
* [BUGFIX] OpenBSD compilation problem fixed.
|
||||
* [BUGFIX] More robust Redis test, with better random port selection.
|
||||
* [BUGFIX] Fix for bug #128 about the RENAME command.
|
||||
* [BUGFIX] Fixed Issue #131. stime/utime reported in INFO was inverted.
|
||||
* [BUGFIX] Unlink Unix socket file on shutdown.
|
||||
* [BUGFIX] AUTH now returns error if no password is set on the server.
|
||||
* [BUGFIX] Exit with Fatal error at startup on RDB loading errors.
|
||||
* redis-check-dump: RDB version 2 now supported.
|
||||
* More informative error when DEBUG RELOAD fails.
|
||||
* Added a config directive for a Unix socket mask.
|
||||
* CONFIG SET/GET for loglevel.
|
||||
|
||||
What's new in Redis 2.3.11 (2.4 Release Candidate 8)
|
||||
====================================================
|
||||
|
||||
|
||||
@@ -34,6 +34,7 @@ port 6379
|
||||
# on a unix socket when not specified.
|
||||
#
|
||||
# unixsocket /tmp/redis.sock
|
||||
# unixsocketperm 755
|
||||
|
||||
# Close the connection after a client is idle for N seconds (0 to disable)
|
||||
timeout 300
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/un.h>
|
||||
#include <netinet/in.h>
|
||||
#include <netinet/tcp.h>
|
||||
@@ -291,7 +292,7 @@ int anetTcpServer(char *err, int port, char *bindaddr)
|
||||
return s;
|
||||
}
|
||||
|
||||
int anetUnixServer(char *err, char *path)
|
||||
int anetUnixServer(char *err, char *path, mode_t perm)
|
||||
{
|
||||
int s;
|
||||
struct sockaddr_un sa;
|
||||
@@ -304,6 +305,8 @@ int anetUnixServer(char *err, char *path)
|
||||
strncpy(sa.sun_path,path,sizeof(sa.sun_path)-1);
|
||||
if (anetListen(err,s,(struct sockaddr*)&sa,sizeof(sa)) == ANET_ERR)
|
||||
return ANET_ERR;
|
||||
if (perm)
|
||||
chmod(sa.sun_path, perm);
|
||||
return s;
|
||||
}
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@ int anetUnixNonBlockConnect(char *err, char *path);
|
||||
int anetRead(int fd, char *buf, int count);
|
||||
int anetResolve(char *err, char *host, char *ipbuf);
|
||||
int anetTcpServer(char *err, int port, char *bindaddr);
|
||||
int anetUnixServer(char *err, char *path);
|
||||
int anetUnixServer(char *err, char *path, mode_t perm);
|
||||
int anetTcpAccept(char *err, int serversock, char *ip, int *port);
|
||||
int anetUnixAccept(char *err, int serversock);
|
||||
int anetWrite(int fd, char *buf, int count);
|
||||
|
||||
31
src/config.c
31
src/config.c
@@ -74,6 +74,11 @@ void loadServerConfig(char *filename) {
|
||||
server.bindaddr = zstrdup(argv[1]);
|
||||
} else if (!strcasecmp(argv[0],"unixsocket") && argc == 2) {
|
||||
server.unixsocket = zstrdup(argv[1]);
|
||||
} else if (!strcasecmp(argv[0],"unixsocketperm") && argc == 2) {
|
||||
server.unixsocketperm = (mode_t)strtol(argv[1], NULL, 8);
|
||||
if (errno || server.unixsocketperm > 0777) {
|
||||
err = "Invalid socket file permissions"; goto loaderr;
|
||||
}
|
||||
} else if (!strcasecmp(argv[0],"save") && argc == 3) {
|
||||
int seconds = atoi(argv[1]);
|
||||
int changes = atoi(argv[2]);
|
||||
@@ -501,6 +506,18 @@ void configSetCommand(redisClient *c) {
|
||||
} else if (!strcasecmp(c->argv[2]->ptr,"slowlog-max-len")) {
|
||||
if (getLongLongFromObject(o,&ll) == REDIS_ERR || ll < 0) goto badfmt;
|
||||
server.slowlog_max_len = (unsigned)ll;
|
||||
} else if (!strcasecmp(c->argv[2]->ptr,"loglevel")) {
|
||||
if (!strcasecmp(o->ptr,"warning")) {
|
||||
server.verbosity = REDIS_WARNING;
|
||||
} else if (!strcasecmp(o->ptr,"notice")) {
|
||||
server.verbosity = REDIS_NOTICE;
|
||||
} else if (!strcasecmp(o->ptr,"verbose")) {
|
||||
server.verbosity = REDIS_VERBOSE;
|
||||
} else if (!strcasecmp(o->ptr,"debug")) {
|
||||
server.verbosity = REDIS_DEBUG;
|
||||
} else {
|
||||
goto badfmt;
|
||||
}
|
||||
} else {
|
||||
addReplyErrorFormat(c,"Unsupported CONFIG parameter: %s",
|
||||
(char*)c->argv[2]->ptr);
|
||||
@@ -681,6 +698,20 @@ void configGetCommand(redisClient *c) {
|
||||
addReplyBulkLongLong(c,server.slowlog_max_len);
|
||||
matches++;
|
||||
}
|
||||
if (stringmatch(pattern,"loglevel",0)) {
|
||||
char *s;
|
||||
|
||||
switch(server.verbosity) {
|
||||
case REDIS_WARNING: s = "warning"; break;
|
||||
case REDIS_VERBOSE: s = "verbose"; break;
|
||||
case REDIS_NOTICE: s = "notice"; break;
|
||||
case REDIS_DEBUG: s = "debug"; break;
|
||||
default: s = "unknown"; break; /* too harmless to panic */
|
||||
}
|
||||
addReplyBulkCString(c,"loglevel");
|
||||
addReplyBulkCString(c,s);
|
||||
matches++;
|
||||
}
|
||||
setDeferredMultiBulkLength(c,replylen,matches*2);
|
||||
}
|
||||
|
||||
|
||||
17
src/db.c
17
src/db.c
@@ -206,7 +206,13 @@ void flushallCommand(redisClient *c) {
|
||||
kill(server.bgsavechildpid,SIGKILL);
|
||||
rdbRemoveTempFile(server.bgsavechildpid);
|
||||
}
|
||||
rdbSave(server.dbfilename);
|
||||
if (server.saveparamslen > 0) {
|
||||
/* Normally rdbSave() will reset dirty, but we don't want this here
|
||||
* as otherwise FLUSHALL will not be replicated nor put into the AOF. */
|
||||
int saved_dirty = server.dirty;
|
||||
rdbSave(server.dbfilename);
|
||||
server.dirty = saved_dirty;
|
||||
}
|
||||
server.dirty++;
|
||||
}
|
||||
|
||||
@@ -341,6 +347,7 @@ void shutdownCommand(redisClient *c) {
|
||||
|
||||
void renameGenericCommand(redisClient *c, int nx) {
|
||||
robj *o;
|
||||
time_t expire;
|
||||
|
||||
/* To use the same key as src and dst is probably an error */
|
||||
if (sdscmp(c->argv[1]->ptr,c->argv[2]->ptr) == 0) {
|
||||
@@ -352,16 +359,18 @@ void renameGenericCommand(redisClient *c, int nx) {
|
||||
return;
|
||||
|
||||
incrRefCount(o);
|
||||
expire = getExpire(c->db,c->argv[1]);
|
||||
if (lookupKeyWrite(c->db,c->argv[2]) != NULL) {
|
||||
if (nx) {
|
||||
decrRefCount(o);
|
||||
addReply(c,shared.czero);
|
||||
return;
|
||||
}
|
||||
dbOverwrite(c->db,c->argv[2],o);
|
||||
} else {
|
||||
dbAdd(c->db,c->argv[2],o);
|
||||
/* Overwrite: delete the old key before creating the new one with the same name. */
|
||||
dbDelete(c->db,c->argv[2]);
|
||||
}
|
||||
dbAdd(c->db,c->argv[2],o);
|
||||
if (expire != -1) setExpire(c->db,c->argv[2],expire);
|
||||
dbDelete(c->db,c->argv[1]);
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
signalModifiedKey(c->db,c->argv[2]);
|
||||
|
||||
@@ -224,7 +224,7 @@ void debugCommand(redisClient *c) {
|
||||
}
|
||||
emptyDb();
|
||||
if (rdbLoad(server.dbfilename) != REDIS_OK) {
|
||||
addReply(c,shared.err);
|
||||
addReplyError(c,"Error trying to load the RDB dump");
|
||||
return;
|
||||
}
|
||||
redisLog(REDIS_WARNING,"DB reloaded by DEBUG RELOAD");
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
#define _BSD_SOURCE
|
||||
|
||||
#ifdef __linux__
|
||||
#if defined(__linux__) || defined(__OpenBSD__)
|
||||
#define _XOPEN_SOURCE 700
|
||||
#else
|
||||
#define _XOPEN_SOURCE
|
||||
|
||||
@@ -937,18 +937,23 @@ int rdbLoad(char *filename) {
|
||||
long loops = 0;
|
||||
|
||||
fp = fopen(filename,"r");
|
||||
if (!fp) return REDIS_ERR;
|
||||
if (!fp) {
|
||||
errno = ENOENT;
|
||||
return REDIS_ERR;
|
||||
}
|
||||
if (fread(buf,9,1,fp) == 0) goto eoferr;
|
||||
buf[9] = '\0';
|
||||
if (memcmp(buf,"REDIS",5) != 0) {
|
||||
fclose(fp);
|
||||
redisLog(REDIS_WARNING,"Wrong signature trying to load DB from file");
|
||||
errno = EINVAL;
|
||||
return REDIS_ERR;
|
||||
}
|
||||
rdbver = atoi(buf+5);
|
||||
if (rdbver < 1 || rdbver > 2) {
|
||||
fclose(fp);
|
||||
redisLog(REDIS_WARNING,"Can't handle RDB format version %d",rdbver);
|
||||
errno = EINVAL;
|
||||
return REDIS_ERR;
|
||||
}
|
||||
|
||||
|
||||
@@ -16,6 +16,10 @@
|
||||
#define REDIS_SET 2
|
||||
#define REDIS_ZSET 3
|
||||
#define REDIS_HASH 4
|
||||
#define REDIS_HASH_ZIPMAP 9
|
||||
#define REDIS_LIST_ZIPLIST 10
|
||||
#define REDIS_SET_INTSET 11
|
||||
#define REDIS_ZSET_ZIPLIST 12
|
||||
|
||||
/* Objects encoding. Some kind of objects like Strings and Hashes can be
|
||||
* internally represented in multiple ways. The 'encoding' field of the object
|
||||
@@ -132,7 +136,7 @@ int processHeader() {
|
||||
}
|
||||
|
||||
dump_version = (int)strtol(buf + 5, NULL, 10);
|
||||
if (dump_version != 1) {
|
||||
if (dump_version < 1 || dump_version > 2) {
|
||||
ERROR("Unknown RDB format version: %d\n", dump_version);
|
||||
}
|
||||
return 1;
|
||||
@@ -144,7 +148,7 @@ int loadType(entry *e) {
|
||||
/* this byte needs to qualify as type */
|
||||
unsigned char t;
|
||||
if (readBytes(&t, 1)) {
|
||||
if (t <= 4 || t >= 253) {
|
||||
if (t <= 4 || (t >=9 && t <= 12) || t >= 253) {
|
||||
e->type = t;
|
||||
return 1;
|
||||
} else {
|
||||
@@ -160,7 +164,8 @@ int loadType(entry *e) {
|
||||
|
||||
int peekType() {
|
||||
unsigned char t;
|
||||
if (readBytes(&t, -1) && (t <= 4 || t >= 253)) return t;
|
||||
if (readBytes(&t, -1) && (t <= 4 || (t >=9 && t <= 12) || t >= 253))
|
||||
return t;
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -375,6 +380,10 @@ int loadPair(entry *e) {
|
||||
|
||||
switch(e->type) {
|
||||
case REDIS_STRING:
|
||||
case REDIS_HASH_ZIPMAP:
|
||||
case REDIS_LIST_ZIPLIST:
|
||||
case REDIS_SET_INTSET:
|
||||
case REDIS_ZSET_ZIPLIST:
|
||||
if (!processStringObject(NULL)) {
|
||||
SHIFT_ERROR(offset, "Error reading entry value");
|
||||
return 0;
|
||||
|
||||
@@ -694,7 +694,7 @@ static void repl() {
|
||||
int repeat, skipargs = 0;
|
||||
|
||||
repeat = atoi(argv[0]);
|
||||
if (repeat) {
|
||||
if (argc > 1 && repeat) {
|
||||
skipargs = 1;
|
||||
} else {
|
||||
repeat = 1;
|
||||
|
||||
24
src/redis.c
24
src/redis.c
@@ -798,6 +798,7 @@ void initServerConfig() {
|
||||
server.port = REDIS_SERVERPORT;
|
||||
server.bindaddr = NULL;
|
||||
server.unixsocket = NULL;
|
||||
server.unixsocketperm = 0;
|
||||
server.ipfd = -1;
|
||||
server.sofd = -1;
|
||||
server.dbnum = REDIS_DEFAULT_DBNUM;
|
||||
@@ -915,7 +916,7 @@ void initServer() {
|
||||
}
|
||||
if (server.unixsocket != NULL) {
|
||||
unlink(server.unixsocket); /* don't care if this fails */
|
||||
server.sofd = anetUnixServer(server.neterr,server.unixsocket);
|
||||
server.sofd = anetUnixServer(server.neterr,server.unixsocket,server.unixsocketperm);
|
||||
if (server.sofd == ANET_ERR) {
|
||||
redisLog(REDIS_WARNING, "Opening socket: %s", server.neterr);
|
||||
exit(1);
|
||||
@@ -1170,6 +1171,10 @@ int prepareForShutdown() {
|
||||
/* Close the listening sockets. Apparently this allows faster restarts. */
|
||||
if (server.ipfd != -1) close(server.ipfd);
|
||||
if (server.sofd != -1) close(server.sofd);
|
||||
if (server.unixsocket) {
|
||||
redisLog(REDIS_NOTICE,"Removing the unix socket file.");
|
||||
unlink(server.unixsocket); /* don't care if this fails */
|
||||
}
|
||||
|
||||
redisLog(REDIS_WARNING,"Redis is now ready to exit, bye bye...");
|
||||
return REDIS_OK;
|
||||
@@ -1178,7 +1183,9 @@ int prepareForShutdown() {
|
||||
/*================================== Commands =============================== */
|
||||
|
||||
void authCommand(redisClient *c) {
|
||||
if (!server.requirepass || !strcmp(c->argv[1]->ptr, server.requirepass)) {
|
||||
if (!server.requirepass) {
|
||||
addReplyError(c,"Client sent AUTH, but no password is set");
|
||||
} else if (!strcmp(c->argv[1]->ptr, server.requirepass)) {
|
||||
c->authenticated = 1;
|
||||
addReply(c,shared.ok);
|
||||
} else {
|
||||
@@ -1285,10 +1292,10 @@ sds genRedisInfoString(void) {
|
||||
uptime,
|
||||
uptime/(3600*24),
|
||||
(unsigned long) server.lruclock,
|
||||
(float)self_ru.ru_utime.tv_sec+(float)self_ru.ru_utime.tv_usec/1000000,
|
||||
(float)self_ru.ru_stime.tv_sec+(float)self_ru.ru_stime.tv_usec/1000000,
|
||||
(float)c_ru.ru_utime.tv_sec+(float)c_ru.ru_utime.tv_usec/1000000,
|
||||
(float)self_ru.ru_utime.tv_sec+(float)self_ru.ru_utime.tv_usec/1000000,
|
||||
(float)c_ru.ru_stime.tv_sec+(float)c_ru.ru_stime.tv_usec/1000000,
|
||||
(float)c_ru.ru_utime.tv_sec+(float)c_ru.ru_utime.tv_usec/1000000,
|
||||
listLength(server.clients)-listLength(server.slaves),
|
||||
listLength(server.slaves),
|
||||
lol, bib,
|
||||
@@ -1644,8 +1651,13 @@ int main(int argc, char **argv) {
|
||||
if (loadAppendOnlyFile(server.appendfilename) == REDIS_OK)
|
||||
redisLog(REDIS_NOTICE,"DB loaded from append only file: %ld seconds",time(NULL)-start);
|
||||
} else {
|
||||
if (rdbLoad(server.dbfilename) == REDIS_OK)
|
||||
redisLog(REDIS_NOTICE,"DB loaded from disk: %ld seconds",time(NULL)-start);
|
||||
if (rdbLoad(server.dbfilename) == REDIS_OK) {
|
||||
redisLog(REDIS_NOTICE,"DB loaded from disk: %ld seconds",
|
||||
time(NULL)-start);
|
||||
} else if (errno != ENOENT) {
|
||||
redisLog(REDIS_WARNING,"Fatal error loading the DB. Exiting.");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
if (server.ipfd > 0)
|
||||
redisLog(REDIS_NOTICE,"The server is now ready to accept connections on port %d", server.port);
|
||||
|
||||
@@ -378,6 +378,7 @@ struct redisServer {
|
||||
int port;
|
||||
char *bindaddr;
|
||||
char *unixsocket;
|
||||
mode_t unixsocketperm;
|
||||
int ipfd;
|
||||
int sofd;
|
||||
redisDb *db;
|
||||
|
||||
@@ -1 +1 @@
|
||||
#define REDIS_VERSION "2.3.11"
|
||||
#define REDIS_VERSION "2.4.1"
|
||||
|
||||
@@ -57,5 +57,11 @@ start_server {tags {"repl"}} {
|
||||
if {$::valgrind} {after 2000}
|
||||
r 0 get mykey
|
||||
} {bar}
|
||||
|
||||
test {FLUSHALL should replicate} {
|
||||
r -1 flushall
|
||||
if {$::valgrind} {after 2000}
|
||||
list [r -1 dbsize] [r 0 dbsize]
|
||||
} {0 0}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -154,7 +154,8 @@ proc start_server {options {code undefined}} {
|
||||
dict set config dir [tmpdir server]
|
||||
|
||||
# start every server on a different port
|
||||
dict set config port [incr ::port]
|
||||
set ::port [find_available_port [expr {$::port+1}]]
|
||||
dict set config port $::port
|
||||
|
||||
# apply overrides from global space and arguments
|
||||
foreach {directive arguments} [concat $::global_overrides $overrides] {
|
||||
|
||||
@@ -37,7 +37,7 @@ set ::all_tests {
|
||||
set ::next_test 0
|
||||
|
||||
set ::host 127.0.0.1
|
||||
set ::port 16379
|
||||
set ::port 21111
|
||||
set ::traceleaks 0
|
||||
set ::valgrind 0
|
||||
set ::verbose 0
|
||||
@@ -148,36 +148,38 @@ proc cleanup {} {
|
||||
puts "OK"
|
||||
}
|
||||
|
||||
proc find_available_port start {
|
||||
for {set j $start} {$j < $start+1024} {incr j} {
|
||||
if {[catch {
|
||||
set fd [socket 127.0.0.1 $start]
|
||||
}]} {
|
||||
return $start
|
||||
} else {
|
||||
close $fd
|
||||
}
|
||||
}
|
||||
if {$j == $start+1024} {
|
||||
error "Can't find a non busy port in the $start-[expr {$start+1023}] range."
|
||||
}
|
||||
}
|
||||
|
||||
proc test_server_main {} {
|
||||
cleanup
|
||||
# Open a listening socket, trying different ports in order to find a
|
||||
# non busy one.
|
||||
set port 11111
|
||||
while 1 {
|
||||
puts "Starting test server at port $port"
|
||||
if {[catch {socket -server accept_test_clients $port} e]} {
|
||||
if {[string match {*address already in use*} $e]} {
|
||||
if {$port == 20000} {
|
||||
puts "Can't find an available TCP port for test server."
|
||||
exit 1
|
||||
} else {
|
||||
incr port
|
||||
}
|
||||
} else {
|
||||
puts "Fatal error starting test server: $e"
|
||||
exit 1
|
||||
}
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
set port [find_available_port 11111]
|
||||
puts "Starting test server at port $port"
|
||||
socket -server accept_test_clients $port
|
||||
|
||||
# Start the client instances
|
||||
set ::clients_pids {}
|
||||
set start_port [expr {$::port+100}]
|
||||
for {set j 0} {$j < $::numclients} {incr j} {
|
||||
set start_port [find_available_port $start_port]
|
||||
set p [exec tclsh8.5 [info script] {*}$::argv \
|
||||
--client $port --port [expr {$::port+($j*10)}] &]
|
||||
--client $port --port $start_port &]
|
||||
lappend ::clients_pids $p
|
||||
incr start_port 10
|
||||
}
|
||||
|
||||
# Setup global state for the test server
|
||||
|
||||
@@ -1,15 +1,27 @@
|
||||
start_server {tags {"auth"}} {
|
||||
test {AUTH fails if there is no password configured server side} {
|
||||
catch {r auth foo} err
|
||||
set _ $err
|
||||
} {ERR*no password*}
|
||||
}
|
||||
|
||||
start_server {tags {"auth"} overrides {requirepass foobar}} {
|
||||
test {AUTH fails when a wrong password is given} {
|
||||
catch {r auth wrong!} err
|
||||
format $err
|
||||
set _ $err
|
||||
} {ERR*invalid password}
|
||||
|
||||
test {Arbitrary command gives an error when AUTH is required} {
|
||||
catch {r set foo bar} err
|
||||
format $err
|
||||
set _ $err
|
||||
} {ERR*operation not permitted}
|
||||
|
||||
test {AUTH succeeds when the right password is given} {
|
||||
r auth foobar
|
||||
} {OK}
|
||||
|
||||
test {Once AUTH succeeded we can actually send commands to the server} {
|
||||
r set foo 100
|
||||
r incr foo
|
||||
} {101}
|
||||
}
|
||||
|
||||
@@ -262,6 +262,25 @@ start_server {tags {"basic"}} {
|
||||
format $err
|
||||
} {ERR*}
|
||||
|
||||
test {RENAME with volatile key, should move the TTL as well} {
|
||||
r del mykey mykey2
|
||||
r set mykey foo
|
||||
r expire mykey 100
|
||||
assert {[r ttl mykey] > 95 && [r ttl mykey] <= 100}
|
||||
r rename mykey mykey2
|
||||
assert {[r ttl mykey2] > 95 && [r ttl mykey2] <= 100}
|
||||
}
|
||||
|
||||
test {RENAME with volatile key, should not inherit TTL of target key} {
|
||||
r del mykey mykey2
|
||||
r set mykey foo
|
||||
r set mykey2 bar
|
||||
r expire mykey2 100
|
||||
assert {[r ttl mykey] == -1 && [r ttl mykey2] > 0}
|
||||
r rename mykey mykey2
|
||||
r ttl mykey2
|
||||
} {-1}
|
||||
|
||||
test {DEL all keys again (DB 0)} {
|
||||
foreach key [r keys *] {
|
||||
r del $key
|
||||
|
||||
Reference in New Issue
Block a user