return dst;
}
-sds catAppendOnlyExpireAtCommand(sds buf, robj *key, robj *seconds) {
- int argc = 3;
- long when;
+/* Create the sds representation of an PEXPIREAT command, using
+ * 'seconds' as time to live and 'cmd' to understand what command
+ * we are translating into a PEXPIREAT.
+ *
+ * This command is used in order to translate EXPIRE and PEXPIRE commands
+ * into PEXPIREAT command so that we retain precision in the append only
+ * file, and the time is always absolute and not relative. */
+sds catAppendOnlyExpireAtCommand(sds buf, struct redisCommand *cmd, robj *key, robj *seconds) {
+ long long when;
robj *argv[3];
/* Make sure we can use strtol */
seconds = getDecodedObject(seconds);
- when = time(NULL)+strtol(seconds->ptr,NULL,10);
+ when = strtoll(seconds->ptr,NULL,10);
+ /* Convert argument into milliseconds for EXPIRE, SETEX, EXPIREAT */
+ if (cmd->proc == expireCommand || cmd->proc == setexCommand ||
+ cmd->proc == expireatCommand)
+ {
+ when *= 1000;
+ }
+ /* Convert into absolute time for EXPIRE, PEXPIRE, SETEX, PSETEX */
+ if (cmd->proc == expireCommand || cmd->proc == pexpireCommand ||
+ cmd->proc == setexCommand || cmd->proc == psetexCommand)
+ {
+ when += mstime();
+ }
decrRefCount(seconds);
- argv[0] = createStringObject("EXPIREAT",8);
+ argv[0] = createStringObject("PEXPIREAT",9);
argv[1] = key;
- argv[2] = createObject(REDIS_STRING,
- sdscatprintf(sdsempty(),"%ld",when));
- buf = catAppendOnlyGenericCommand(buf, argc, argv);
+ argv[2] = createStringObjectFromLongLong(when);
+ buf = catAppendOnlyGenericCommand(buf, 3, argv);
decrRefCount(argv[0]);
decrRefCount(argv[2]);
return buf;
server.appendseldb = dictid;
}
- if (cmd->proc == expireCommand) {
- /* Translate EXPIRE into EXPIREAT */
- buf = catAppendOnlyExpireAtCommand(buf,argv[1],argv[2]);
- } else if (cmd->proc == setexCommand) {
- /* Translate SETEX to SET and EXPIREAT */
+ if (cmd->proc == expireCommand || cmd->proc == pexpireCommand ||
+ cmd->proc == expireatCommand) {
+ /* Translate EXPIRE/PEXPIRE/EXPIREAT into PEXPIREAT */
+ buf = catAppendOnlyExpireAtCommand(buf,cmd,argv[1],argv[2]);
+ } else if (cmd->proc == setexCommand || cmd->proc == psetexCommand) {
+ /* Translate SETEX/PSETEX to SET and PEXPIREAT */
tmpargv[0] = createStringObject("SET",3);
tmpargv[1] = argv[1];
tmpargv[2] = argv[3];
buf = catAppendOnlyGenericCommand(buf,3,tmpargv);
decrRefCount(tmpargv[0]);
- buf = catAppendOnlyExpireAtCommand(buf,argv[1],argv[2]);
+ buf = catAppendOnlyExpireAtCommand(buf,cmd,argv[1],argv[2]);
} else {
+ /* All the other commands don't need translation or need the
+ * same translation already operated in the command vector
+ * for the replication itself. */
buf = catAppendOnlyGenericCommand(buf,argc,argv);
}
}
/* Save the expire time */
if (expiretime != -1) {
- char cmd[]="*4\r\n$8\r\nEXPIREAT\r\n";
+ char cmd[]="*3\r\n$9\r\nPEXPIREAT\r\n";
/* If this key is already expired skip it */
if (expiretime < now) continue;
if (rioWrite(&aof,cmd,sizeof(cmd)-1) == 0) goto werr;
if (rioWriteBulkObject(&aof,&key) == 0) goto werr;
if (rioWriteBulkLongLong(&aof,expiretime) == 0) goto werr;
- if (rioWriteBulkString(&aof,"ms",2) == 0) goto werr;
}
}
dictReleaseIterator(di);
return tolower(arg[0]) == 'm' && tolower(arg[1]) == 's' && arg[2] == '\0';
}
-void expireGenericCommand(redisClient *c, long long offset) {
+void expireGenericCommand(redisClient *c, long long offset, int unit) {
dictEntry *de;
robj *key = c->argv[1], *param = c->argv[2];
long long milliseconds;
- int time_in_seconds = 1;
if (getLongLongFromObjectOrReply(c, param, &milliseconds, NULL) != REDIS_OK)
return;
- /* If no "ms" argument was passed the time is in second, so we need
- * to multilpy it by 1000 */
- if (c->argc == 4) {
- if (!stringObjectEqualsMs(c->argv[3])) {
- addReply(c,shared.syntaxerr);
- return;
- }
- time_in_seconds = 0; /* "ms" argument passed. */
- }
- if (time_in_seconds) milliseconds *= 1000;
+ if (unit == UNIT_SECONDS) milliseconds *= 1000;
milliseconds -= offset;
de = dictFind(c->db->dict,key->ptr);
}
void expireCommand(redisClient *c) {
- if (c->argc > 4) {
- addReply(c,shared.syntaxerr);
- return;
- }
- expireGenericCommand(c,0);
+ expireGenericCommand(c,0,UNIT_SECONDS);
}
void expireatCommand(redisClient *c) {
- if (c->argc > 4) {
- addReply(c,shared.syntaxerr);
- return;
- }
- expireGenericCommand(c,mstime());
+ expireGenericCommand(c,mstime(),UNIT_SECONDS);
}
-void ttlCommand(redisClient *c) {
- long long expire, ttl = -1;
- int output_ms = 0;
+void pexpireCommand(redisClient *c) {
+ expireGenericCommand(c,0,UNIT_MILLISECONDS);
+}
- if (c->argc == 3) {
- if (stringObjectEqualsMs(c->argv[2])) {
- output_ms = 1;
- } else {
- addReply(c,shared.syntaxerr);
- return;
- }
- }
+void pexpireatCommand(redisClient *c) {
+ expireGenericCommand(c,mstime(),UNIT_MILLISECONDS);
+}
+
+void ttlGenericCommand(redisClient *c, int output_ms) {
+ long long expire, ttl = -1;
expire = getExpire(c->db,c->argv[1]);
if (expire != -1) {
}
}
+void ttlCommand(redisClient *c) {
+ ttlGenericCommand(c, 0);
+}
+
+void pttlCommand(redisClient *c) {
+ ttlGenericCommand(c, 1);
+}
+
void persistCommand(redisClient *c) {
dictEntry *de;
{"set",setCommand,3,"wm",0,noPreloadGetKeys,1,1,1,0,0},
{"setnx",setnxCommand,3,"wm",0,noPreloadGetKeys,1,1,1,0,0},
{"setex",setexCommand,4,"wm",0,noPreloadGetKeys,2,2,1,0,0},
+ {"psetex",psetexCommand,4,"wm",0,noPreloadGetKeys,2,2,1,0,0},
{"append",appendCommand,3,"wm",0,NULL,1,1,1,0,0},
{"strlen",strlenCommand,2,"r",0,NULL,1,1,1,0,0},
{"del",delCommand,-2,"w",0,noPreloadGetKeys,1,-1,1,0,0},
{"move",moveCommand,3,"w",0,NULL,1,1,1,0,0},
{"rename",renameCommand,3,"w",0,renameGetKeys,1,2,1,0,0},
{"renamenx",renamenxCommand,3,"w",0,renameGetKeys,1,2,1,0,0},
- {"expire",expireCommand,-3,"w",0,NULL,1,1,1,0,0},
- {"expireat",expireatCommand,-3,"w",0,NULL,1,1,1,0,0},
+ {"expire",expireCommand,3,"w",0,NULL,1,1,1,0,0},
+ {"expireat",expireatCommand,3,"w",0,NULL,1,1,1,0,0},
+ {"pexpire",pexpireCommand,3,"w",0,NULL,1,1,1,0,0},
+ {"pexpireat",pexpireatCommand,3,"w",0,NULL,1,1,1,0,0},
{"keys",keysCommand,2,"r",0,NULL,0,0,0,0,0},
{"dbsize",dbsizeCommand,1,"r",0,NULL,0,0,0,0,0},
{"auth",authCommand,2,"r",0,NULL,0,0,0,0,0},
{"sort",sortCommand,-2,"wm",0,NULL,1,1,1,0,0},
{"info",infoCommand,-1,"r",0,NULL,0,0,0,0,0},
{"monitor",monitorCommand,1,"ars",0,NULL,0,0,0,0,0},
- {"ttl",ttlCommand,-2,"r",0,NULL,1,1,1,0,0},
+ {"ttl",ttlCommand,2,"r",0,NULL,1,1,1,0,0},
+ {"pttl",pttlCommand,2,"r",0,NULL,1,1,1,0,0},
{"persist",persistCommand,2,"w",0,NULL,1,1,1,0,0},
{"slaveof",slaveofCommand,3,"aws",0,NULL,0,0,0,0,0},
{"debug",debugCommand,-2,"aw",0,NULL,0,0,0,0,0},
/* Scripting */
#define REDIS_LUA_TIME_LIMIT 5000 /* milliseconds */
+/* Units */
+#define UNIT_SECONDS 0
+#define UNIT_MILLISECONDS 1
+
/* We can print the stacktrace, so our assert is defined this way: */
#define redisAssertWithInfo(_c,_o,_e) ((_e)?(void)0 : (_redisAssertWithInfo(_c,_o,#_e,__FILE__,__LINE__),_exit(1)))
#define redisAssert(_e) ((_e)?(void)0 : (_redisAssert(#_e,__FILE__,__LINE__),_exit(1)))
void setCommand(redisClient *c);
void setnxCommand(redisClient *c);
void setexCommand(redisClient *c);
+void psetexCommand(redisClient *c);
void getCommand(redisClient *c);
void delCommand(redisClient *c);
void existsCommand(redisClient *c);
void monitorCommand(redisClient *c);
void expireCommand(redisClient *c);
void expireatCommand(redisClient *c);
+void pexpireCommand(redisClient *c);
+void pexpireatCommand(redisClient *c);
void getsetCommand(redisClient *c);
void ttlCommand(redisClient *c);
+void pttlCommand(redisClient *c);
void persistCommand(redisClient *c);
void slaveofCommand(redisClient *c);
void debugCommand(redisClient *c);
return REDIS_OK;
}
-void setGenericCommand(redisClient *c, int nx, robj *key, robj *val, robj *expire) {
- long seconds = 0; /* initialized to avoid an harmness warning */
+void setGenericCommand(redisClient *c, int nx, robj *key, robj *val, robj *expire, int unit) {
+ long long milliseconds = 0; /* initialized to avoid an harmness warning */
if (expire) {
- if (getLongFromObjectOrReply(c, expire, &seconds, NULL) != REDIS_OK)
+ if (getLongLongFromObjectOrReply(c, expire, &milliseconds, NULL) != REDIS_OK)
return;
- if (seconds <= 0) {
+ if (milliseconds <= 0) {
addReplyError(c,"invalid expire time in SETEX");
return;
}
+ if (unit == UNIT_SECONDS) milliseconds *= 1000;
}
if (lookupKeyWrite(c->db,key) != NULL && nx) {
}
setKey(c->db,key,val);
server.dirty++;
- if (expire) setExpire(c->db,key,(time(NULL)+seconds)*1000);
+ if (expire) setExpire(c->db,key,mstime()+milliseconds);
addReply(c, nx ? shared.cone : shared.ok);
}
void setCommand(redisClient *c) {
c->argv[2] = tryObjectEncoding(c->argv[2]);
- setGenericCommand(c,0,c->argv[1],c->argv[2],NULL);
+ setGenericCommand(c,0,c->argv[1],c->argv[2],NULL,0);
}
void setnxCommand(redisClient *c) {
c->argv[2] = tryObjectEncoding(c->argv[2]);
- setGenericCommand(c,1,c->argv[1],c->argv[2],NULL);
+ setGenericCommand(c,1,c->argv[1],c->argv[2],NULL,0);
}
void setexCommand(redisClient *c) {
c->argv[3] = tryObjectEncoding(c->argv[3]);
- setGenericCommand(c,0,c->argv[1],c->argv[3],c->argv[2]);
+ setGenericCommand(c,0,c->argv[1],c->argv[3],c->argv[2],UNIT_SECONDS);
+}
+
+void psetexCommand(redisClient *c) {
+ c->argv[3] = tryObjectEncoding(c->argv[3]);
+ setGenericCommand(c,0,c->argv[1],c->argv[3],c->argv[2],UNIT_MILLISECONDS);
}
int getGenericCommand(redisClient *c) {