2012-11-08 12:25:23 -05:00
|
|
|
/*
|
|
|
|
* Copyright (c) 2009-2012, Salvatore Sanfilippo <antirez at gmail dot com>
|
|
|
|
* All rights reserved.
|
|
|
|
*
|
|
|
|
* Redistribution and use in source and binary forms, with or without
|
|
|
|
* modification, are permitted provided that the following conditions are met:
|
|
|
|
*
|
|
|
|
* * Redistributions of source code must retain the above copyright notice,
|
|
|
|
* this list of conditions and the following disclaimer.
|
|
|
|
* * Redistributions in binary form must reproduce the above copyright
|
|
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
|
|
* documentation and/or other materials provided with the distribution.
|
|
|
|
* * Neither the name of Redis nor the names of its contributors may be used
|
|
|
|
* to endorse or promote products derived from this software without
|
|
|
|
* specific prior written permission.
|
|
|
|
*
|
|
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
|
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
|
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
|
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
|
|
|
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
|
|
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
|
|
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
|
|
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
|
|
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
|
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
|
|
* POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
*/
|
|
|
|
|
2015-07-26 09:14:57 -04:00
|
|
|
#include "server.h"
|
2011-11-12 13:27:35 -05:00
|
|
|
#include <math.h> /* isnan(), isinf() */
|
2010-06-21 18:07:48 -04:00
|
|
|
|
|
|
|
/*-----------------------------------------------------------------------------
|
|
|
|
* String Commands
|
|
|
|
*----------------------------------------------------------------------------*/
|
|
|
|
|
2015-07-26 09:20:46 -04:00
|
|
|
static int checkStringLength(client *c, long long size) {
|
2010-12-14 08:20:51 -05:00
|
|
|
if (size > 512*1024*1024) {
|
|
|
|
addReplyError(c,"string exceeds maximum allowed size (512MB)");
|
2015-07-26 17:17:55 -04:00
|
|
|
return C_ERR;
|
2010-12-14 08:20:51 -05:00
|
|
|
}
|
2015-07-26 17:17:55 -04:00
|
|
|
return C_OK;
|
2010-12-14 08:20:51 -05:00
|
|
|
}
|
|
|
|
|
2013-03-28 10:40:19 -04:00
|
|
|
/* The setGenericCommand() function implements the SET operation with different
|
|
|
|
* options and variants. This function is called in order to implement the
|
|
|
|
* following commands: SET, SETEX, PSETEX, SETNX.
|
|
|
|
*
|
|
|
|
* 'flags' changes the behavior of the command (NX or XX, see belove).
|
|
|
|
*
|
|
|
|
* 'expire' represents an expire to set in form of a Redis object as passed
|
|
|
|
* by the user. It is interpreted according to the specified 'unit'.
|
|
|
|
*
|
|
|
|
* 'ok_reply' and 'abort_reply' is what the function will reply to the client
|
|
|
|
* if the operation is performed, or when it is not because of NX or
|
|
|
|
* XX flags.
|
|
|
|
*
|
|
|
|
* If ok_reply is NULL "+OK" is used.
|
|
|
|
* If abort_reply is NULL, "$-1" is used. */
|
|
|
|
|
2015-07-26 09:28:00 -04:00
|
|
|
#define OBJ_SET_NO_FLAGS 0
|
|
|
|
#define OBJ_SET_NX (1<<0) /* Set if key not exists. */
|
|
|
|
#define OBJ_SET_XX (1<<1) /* Set if key exists. */
|
|
|
|
#define OBJ_SET_EX (1<<2) /* Set if time in seconds is given */
|
|
|
|
#define OBJ_SET_PX (1<<3) /* Set if time in ms in given */
|
2013-03-28 10:40:19 -04:00
|
|
|
|
2015-07-26 09:20:46 -04:00
|
|
|
void setGenericCommand(client *c, int flags, robj *key, robj *val, robj *expire, int unit, robj *ok_reply, robj *abort_reply) {
|
2013-01-16 12:00:20 -05:00
|
|
|
long long milliseconds = 0; /* initialized to avoid any harmness warning */
|
2010-06-21 18:07:48 -04:00
|
|
|
|
|
|
|
if (expire) {
|
2015-07-26 17:17:55 -04:00
|
|
|
if (getLongLongFromObjectOrReply(c, expire, &milliseconds, NULL) != C_OK)
|
2010-06-21 18:07:48 -04:00
|
|
|
return;
|
2011-11-10 11:52:02 -05:00
|
|
|
if (milliseconds <= 0) {
|
2014-08-18 05:15:50 -04:00
|
|
|
addReplyErrorFormat(c,"invalid expire time in %s",c->cmd->name);
|
2010-06-21 18:07:48 -04:00
|
|
|
return;
|
|
|
|
}
|
2011-11-10 11:52:02 -05:00
|
|
|
if (unit == UNIT_SECONDS) milliseconds *= 1000;
|
2010-06-21 18:07:48 -04:00
|
|
|
}
|
|
|
|
|
2015-07-26 09:28:00 -04:00
|
|
|
if ((flags & OBJ_SET_NX && lookupKeyWrite(c->db,key) != NULL) ||
|
|
|
|
(flags & OBJ_SET_XX && lookupKeyWrite(c->db,key) == NULL))
|
2013-03-28 10:40:19 -04:00
|
|
|
{
|
|
|
|
addReply(c, abort_reply ? abort_reply : shared.nullbulk);
|
2011-06-14 09:34:27 -04:00
|
|
|
return;
|
2010-06-21 18:07:48 -04:00
|
|
|
}
|
2011-06-14 09:34:27 -04:00
|
|
|
setKey(c->db,key,val);
|
2010-06-21 18:07:48 -04:00
|
|
|
server.dirty++;
|
Replication: fix the infamous key leakage of writable slaves + EXPIRE.
BACKGROUND AND USE CASEj
Redis slaves are normally write only, however the supprot a "writable"
mode which is very handy when scaling reads on slaves, that actually
need write operations in order to access data. For instance imagine
having slaves replicating certain Sets keys from the master. When
accessing the data on the slave, we want to peform intersections between
such Sets values. However we don't want to intersect each time: to cache
the intersection for some time often is a good idea.
To do so, it is possible to setup a slave as a writable slave, and
perform the intersection on the slave side, perhaps setting a TTL on the
resulting key so that it will expire after some time.
THE BUG
Problem: in order to have a consistent replication, expiring of keys in
Redis replication is up to the master, that synthesize DEL operations to
send in the replication stream. However slaves logically expire keys
by hiding them from read attempts from clients so that if the master did
not promptly sent a DEL, the client still see logically expired keys
as non existing.
Because slaves don't actively expire keys by actually evicting them but
just masking from the POV of read operations, if a key is created in a
writable slave, and an expire is set, the key will be leaked forever:
1. No DEL will be received from the master, which does not know about
such a key at all.
2. No eviction will be performed by the slave, since it needs to disable
eviction because it's up to masters, otherwise consistency of data is
lost.
THE FIX
In order to fix the problem, the slave should be able to tag keys that
were created in the slave side and have an expire set in some way.
My solution involved using an unique additional dictionary created by
the writable slave only if needed. The dictionary is obviously keyed by
the key name that we need to track: all the keys that are set with an
expire directly by a client writing to the slave are tracked.
The value in the dictionary is a bitmap of all the DBs where such a key
name need to be tracked, so that we can use a single dictionary to track
keys in all the DBs used by the slave (actually this limits the solution
to the first 64 DBs, but the default with Redis is to use 16 DBs).
This solution allows to pay both a small complexity and CPU penalty,
which is zero when the feature is not used, actually. The slave-side
eviction is encapsulated in code which is not coupled with the rest of
the Redis core, if not for the hook to track the keys.
TODO
I'm doing the first smoke tests to see if the feature works as expected:
so far so good. Unit tests should be added before merging into the
4.0 branch.
2016-12-13 04:20:06 -05:00
|
|
|
if (expire) setExpire(c,c->db,key,mstime()+milliseconds);
|
2015-07-27 03:41:48 -04:00
|
|
|
notifyKeyspaceEvent(NOTIFY_STRING,"set",key,c->db->id);
|
|
|
|
if (expire) notifyKeyspaceEvent(NOTIFY_GENERIC,
|
2013-01-25 07:19:08 -05:00
|
|
|
"expire",key,c->db->id);
|
2013-03-28 10:40:19 -04:00
|
|
|
addReply(c, ok_reply ? ok_reply : shared.ok);
|
2010-06-21 18:07:48 -04:00
|
|
|
}
|
|
|
|
|
2013-03-28 10:40:19 -04:00
|
|
|
/* SET key value [NX] [XX] [EX <seconds>] [PX <milliseconds>] */
|
2015-07-26 09:20:46 -04:00
|
|
|
void setCommand(client *c) {
|
2013-03-28 10:40:19 -04:00
|
|
|
int j;
|
|
|
|
robj *expire = NULL;
|
|
|
|
int unit = UNIT_SECONDS;
|
2015-07-26 09:28:00 -04:00
|
|
|
int flags = OBJ_SET_NO_FLAGS;
|
2013-03-28 10:40:19 -04:00
|
|
|
|
|
|
|
for (j = 3; j < c->argc; j++) {
|
|
|
|
char *a = c->argv[j]->ptr;
|
|
|
|
robj *next = (j == c->argc-1) ? NULL : c->argv[j+1];
|
|
|
|
|
2013-03-28 17:41:46 -04:00
|
|
|
if ((a[0] == 'n' || a[0] == 'N') &&
|
2014-12-14 10:12:58 -05:00
|
|
|
(a[1] == 'x' || a[1] == 'X') && a[2] == '\0' &&
|
2015-07-26 09:28:00 -04:00
|
|
|
!(flags & OBJ_SET_XX))
|
2015-02-03 08:17:06 -05:00
|
|
|
{
|
2015-07-26 09:28:00 -04:00
|
|
|
flags |= OBJ_SET_NX;
|
2013-03-28 17:41:46 -04:00
|
|
|
} else if ((a[0] == 'x' || a[0] == 'X') &&
|
2014-12-14 10:12:58 -05:00
|
|
|
(a[1] == 'x' || a[1] == 'X') && a[2] == '\0' &&
|
2015-07-26 09:28:00 -04:00
|
|
|
!(flags & OBJ_SET_NX))
|
2015-02-03 08:17:06 -05:00
|
|
|
{
|
2015-07-26 09:28:00 -04:00
|
|
|
flags |= OBJ_SET_XX;
|
2013-03-28 17:41:46 -04:00
|
|
|
} else if ((a[0] == 'e' || a[0] == 'E') &&
|
2014-12-14 10:12:58 -05:00
|
|
|
(a[1] == 'x' || a[1] == 'X') && a[2] == '\0' &&
|
2015-07-26 09:28:00 -04:00
|
|
|
!(flags & OBJ_SET_PX) && next)
|
2015-02-03 08:17:06 -05:00
|
|
|
{
|
2015-07-26 09:28:00 -04:00
|
|
|
flags |= OBJ_SET_EX;
|
2013-03-28 10:40:19 -04:00
|
|
|
unit = UNIT_SECONDS;
|
|
|
|
expire = next;
|
|
|
|
j++;
|
2013-03-28 17:41:46 -04:00
|
|
|
} else if ((a[0] == 'p' || a[0] == 'P') &&
|
2014-12-14 10:12:58 -05:00
|
|
|
(a[1] == 'x' || a[1] == 'X') && a[2] == '\0' &&
|
2015-07-26 09:28:00 -04:00
|
|
|
!(flags & OBJ_SET_EX) && next)
|
2015-02-03 08:17:06 -05:00
|
|
|
{
|
2015-07-26 09:28:00 -04:00
|
|
|
flags |= OBJ_SET_PX;
|
2013-03-28 10:40:19 -04:00
|
|
|
unit = UNIT_MILLISECONDS;
|
|
|
|
expire = next;
|
|
|
|
j++;
|
|
|
|
} else {
|
|
|
|
addReply(c,shared.syntaxerr);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2010-10-17 11:21:41 -04:00
|
|
|
c->argv[2] = tryObjectEncoding(c->argv[2]);
|
2013-03-28 10:40:19 -04:00
|
|
|
setGenericCommand(c,flags,c->argv[1],c->argv[2],expire,unit,NULL,NULL);
|
2010-06-21 18:07:48 -04:00
|
|
|
}
|
|
|
|
|
2015-07-26 09:20:46 -04:00
|
|
|
void setnxCommand(client *c) {
|
2010-10-17 11:21:41 -04:00
|
|
|
c->argv[2] = tryObjectEncoding(c->argv[2]);
|
2015-07-26 09:28:00 -04:00
|
|
|
setGenericCommand(c,OBJ_SET_NX,c->argv[1],c->argv[2],NULL,0,shared.cone,shared.czero);
|
2010-06-21 18:07:48 -04:00
|
|
|
}
|
|
|
|
|
2015-07-26 09:20:46 -04:00
|
|
|
void setexCommand(client *c) {
|
2010-10-17 11:21:41 -04:00
|
|
|
c->argv[3] = tryObjectEncoding(c->argv[3]);
|
2015-07-26 09:28:00 -04:00
|
|
|
setGenericCommand(c,OBJ_SET_NO_FLAGS,c->argv[1],c->argv[3],c->argv[2],UNIT_SECONDS,NULL,NULL);
|
2011-11-10 11:52:02 -05:00
|
|
|
}
|
|
|
|
|
2015-07-26 09:20:46 -04:00
|
|
|
void psetexCommand(client *c) {
|
2011-11-10 11:52:02 -05:00
|
|
|
c->argv[3] = tryObjectEncoding(c->argv[3]);
|
2015-07-26 09:28:00 -04:00
|
|
|
setGenericCommand(c,OBJ_SET_NO_FLAGS,c->argv[1],c->argv[3],c->argv[2],UNIT_MILLISECONDS,NULL,NULL);
|
2010-06-21 18:07:48 -04:00
|
|
|
}
|
|
|
|
|
2015-07-26 09:20:46 -04:00
|
|
|
int getGenericCommand(client *c) {
|
2010-06-21 18:07:48 -04:00
|
|
|
robj *o;
|
|
|
|
|
|
|
|
if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.nullbulk)) == NULL)
|
2015-07-26 17:17:55 -04:00
|
|
|
return C_OK;
|
2010-06-21 18:07:48 -04:00
|
|
|
|
2015-07-26 09:28:00 -04:00
|
|
|
if (o->type != OBJ_STRING) {
|
2010-06-21 18:07:48 -04:00
|
|
|
addReply(c,shared.wrongtypeerr);
|
2015-07-26 17:17:55 -04:00
|
|
|
return C_ERR;
|
2010-06-21 18:07:48 -04:00
|
|
|
} else {
|
|
|
|
addReplyBulk(c,o);
|
2015-07-26 17:17:55 -04:00
|
|
|
return C_OK;
|
2010-06-21 18:07:48 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-07-26 09:20:46 -04:00
|
|
|
void getCommand(client *c) {
|
2010-06-21 18:07:48 -04:00
|
|
|
getGenericCommand(c);
|
|
|
|
}
|
|
|
|
|
2015-07-26 09:20:46 -04:00
|
|
|
void getsetCommand(client *c) {
|
2015-07-26 17:17:55 -04:00
|
|
|
if (getGenericCommand(c) == C_ERR) return;
|
2010-10-17 11:21:41 -04:00
|
|
|
c->argv[2] = tryObjectEncoding(c->argv[2]);
|
2011-06-14 09:34:27 -04:00
|
|
|
setKey(c->db,c->argv[1],c->argv[2]);
|
2015-07-27 03:41:48 -04:00
|
|
|
notifyKeyspaceEvent(NOTIFY_STRING,"set",c->argv[1],c->db->id);
|
2010-06-21 18:07:48 -04:00
|
|
|
server.dirty++;
|
2010-12-09 10:39:33 -05:00
|
|
|
}
|
|
|
|
|
2015-07-26 09:20:46 -04:00
|
|
|
void setrangeCommand(client *c) {
|
2010-12-14 08:20:51 -05:00
|
|
|
robj *o;
|
|
|
|
long offset;
|
|
|
|
sds value = c->argv[3]->ptr;
|
|
|
|
|
2015-07-26 17:17:55 -04:00
|
|
|
if (getLongFromObjectOrReply(c,c->argv[2],&offset,NULL) != C_OK)
|
2010-12-14 08:20:51 -05:00
|
|
|
return;
|
|
|
|
|
2010-12-15 05:30:50 -05:00
|
|
|
if (offset < 0) {
|
|
|
|
addReplyError(c,"offset is out of range");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2010-12-14 08:20:51 -05:00
|
|
|
o = lookupKeyWrite(c->db,c->argv[1]);
|
|
|
|
if (o == NULL) {
|
|
|
|
/* Return 0 when setting nothing on a non-existing string */
|
|
|
|
if (sdslen(value) == 0) {
|
|
|
|
addReply(c,shared.czero);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Return when the resulting string exceeds allowed size */
|
2015-07-26 17:17:55 -04:00
|
|
|
if (checkStringLength(c,offset+sdslen(value)) != C_OK)
|
2010-12-14 08:20:51 -05:00
|
|
|
return;
|
|
|
|
|
2015-07-26 09:28:00 -04:00
|
|
|
o = createObject(OBJ_STRING,sdsnewlen(NULL, offset+sdslen(value)));
|
2010-12-14 08:20:51 -05:00
|
|
|
dbAdd(c->db,c->argv[1],o);
|
|
|
|
} else {
|
2010-12-15 05:49:04 -05:00
|
|
|
size_t olen;
|
2010-12-14 08:20:51 -05:00
|
|
|
|
|
|
|
/* Key exists, check type */
|
2015-07-26 09:28:00 -04:00
|
|
|
if (checkType(c,o,OBJ_STRING))
|
2010-12-14 08:20:51 -05:00
|
|
|
return;
|
|
|
|
|
|
|
|
/* Return existing string length when setting nothing */
|
2010-12-15 05:49:04 -05:00
|
|
|
olen = stringObjectLen(o);
|
2010-12-14 08:20:51 -05:00
|
|
|
if (sdslen(value) == 0) {
|
|
|
|
addReplyLongLong(c,olen);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Return when the resulting string exceeds allowed size */
|
2015-07-26 17:17:55 -04:00
|
|
|
if (checkStringLength(c,offset+sdslen(value)) != C_OK)
|
2010-12-14 08:20:51 -05:00
|
|
|
return;
|
|
|
|
|
|
|
|
/* Create a copy when the object is shared or encoded. */
|
2014-03-30 12:32:17 -04:00
|
|
|
o = dbUnshareStringValue(c->db,c->argv[1],o);
|
2010-12-14 08:20:51 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
if (sdslen(value) > 0) {
|
|
|
|
o->ptr = sdsgrowzero(o->ptr,offset+sdslen(value));
|
|
|
|
memcpy((char*)o->ptr+offset,value,sdslen(value));
|
2010-12-29 13:39:42 -05:00
|
|
|
signalModifiedKey(c->db,c->argv[1]);
|
2015-07-27 03:41:48 -04:00
|
|
|
notifyKeyspaceEvent(NOTIFY_STRING,
|
2013-01-25 07:19:08 -05:00
|
|
|
"setrange",c->argv[1],c->db->id);
|
2010-12-14 08:20:51 -05:00
|
|
|
server.dirty++;
|
|
|
|
}
|
|
|
|
addReplyLongLong(c,sdslen(o->ptr));
|
|
|
|
}
|
|
|
|
|
2015-07-26 09:20:46 -04:00
|
|
|
void getrangeCommand(client *c) {
|
2010-12-14 09:10:58 -05:00
|
|
|
robj *o;
|
2014-09-02 17:56:17 -04:00
|
|
|
long long start, end;
|
2010-12-14 09:10:58 -05:00
|
|
|
char *str, llbuf[32];
|
|
|
|
size_t strlen;
|
|
|
|
|
2015-07-26 17:17:55 -04:00
|
|
|
if (getLongLongFromObjectOrReply(c,c->argv[2],&start,NULL) != C_OK)
|
2010-12-14 09:10:58 -05:00
|
|
|
return;
|
2015-07-26 17:17:55 -04:00
|
|
|
if (getLongLongFromObjectOrReply(c,c->argv[3],&end,NULL) != C_OK)
|
2010-12-14 09:10:58 -05:00
|
|
|
return;
|
2011-03-04 10:22:50 -05:00
|
|
|
if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.emptybulk)) == NULL ||
|
2015-07-26 09:28:00 -04:00
|
|
|
checkType(c,o,OBJ_STRING)) return;
|
2010-12-14 09:10:58 -05:00
|
|
|
|
2015-07-26 09:28:00 -04:00
|
|
|
if (o->encoding == OBJ_ENCODING_INT) {
|
2010-12-14 09:10:58 -05:00
|
|
|
str = llbuf;
|
|
|
|
strlen = ll2string(llbuf,sizeof(llbuf),(long)o->ptr);
|
|
|
|
} else {
|
|
|
|
str = o->ptr;
|
|
|
|
strlen = sdslen(str);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Convert negative indexes */
|
2016-06-15 06:48:58 -04:00
|
|
|
if (start < 0 && end < 0 && start > end) {
|
|
|
|
addReply(c,shared.emptybulk);
|
|
|
|
return;
|
|
|
|
}
|
2010-12-14 09:10:58 -05:00
|
|
|
if (start < 0) start = strlen+start;
|
|
|
|
if (end < 0) end = strlen+end;
|
|
|
|
if (start < 0) start = 0;
|
|
|
|
if (end < 0) end = 0;
|
2014-09-02 17:56:17 -04:00
|
|
|
if ((unsigned long long)end >= strlen) end = strlen-1;
|
2010-12-14 09:10:58 -05:00
|
|
|
|
|
|
|
/* Precondition: end >= 0 && end < strlen, so the only condition where
|
|
|
|
* nothing can be returned is: start > end. */
|
2014-09-02 18:56:28 -04:00
|
|
|
if (start > end || strlen == 0) {
|
2011-03-04 10:22:50 -05:00
|
|
|
addReply(c,shared.emptybulk);
|
2010-12-14 09:10:58 -05:00
|
|
|
} else {
|
|
|
|
addReplyBulkCBuffer(c,(char*)str+start,end-start+1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-07-26 09:20:46 -04:00
|
|
|
void mgetCommand(client *c) {
|
2010-06-21 18:07:48 -04:00
|
|
|
int j;
|
|
|
|
|
2010-09-02 06:38:34 -04:00
|
|
|
addReplyMultiBulkLen(c,c->argc-1);
|
2010-06-21 18:07:48 -04:00
|
|
|
for (j = 1; j < c->argc; j++) {
|
|
|
|
robj *o = lookupKeyRead(c->db,c->argv[j]);
|
|
|
|
if (o == NULL) {
|
|
|
|
addReply(c,shared.nullbulk);
|
|
|
|
} else {
|
2015-07-26 09:28:00 -04:00
|
|
|
if (o->type != OBJ_STRING) {
|
2010-06-21 18:07:48 -04:00
|
|
|
addReply(c,shared.nullbulk);
|
|
|
|
} else {
|
|
|
|
addReplyBulk(c,o);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-07-26 09:20:46 -04:00
|
|
|
void msetGenericCommand(client *c, int nx) {
|
2010-06-21 18:07:48 -04:00
|
|
|
int j, busykeys = 0;
|
|
|
|
|
|
|
|
if ((c->argc % 2) == 0) {
|
2010-09-02 13:52:24 -04:00
|
|
|
addReplyError(c,"wrong number of arguments for MSET");
|
2010-06-21 18:07:48 -04:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
/* Handle the NX flag. The MSETNX semantic is to return zero and don't
|
|
|
|
* set nothing at all if at least one already key exists. */
|
|
|
|
if (nx) {
|
|
|
|
for (j = 1; j < c->argc; j += 2) {
|
|
|
|
if (lookupKeyWrite(c->db,c->argv[j]) != NULL) {
|
|
|
|
busykeys++;
|
|
|
|
}
|
|
|
|
}
|
2011-06-14 09:34:27 -04:00
|
|
|
if (busykeys) {
|
|
|
|
addReply(c, shared.czero);
|
|
|
|
return;
|
|
|
|
}
|
2010-06-21 18:07:48 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
for (j = 1; j < c->argc; j += 2) {
|
|
|
|
c->argv[j+1] = tryObjectEncoding(c->argv[j+1]);
|
2011-06-14 09:34:27 -04:00
|
|
|
setKey(c->db,c->argv[j],c->argv[j+1]);
|
2015-07-27 03:41:48 -04:00
|
|
|
notifyKeyspaceEvent(NOTIFY_STRING,"set",c->argv[j],c->db->id);
|
2010-06-21 18:07:48 -04:00
|
|
|
}
|
|
|
|
server.dirty += (c->argc-1)/2;
|
|
|
|
addReply(c, nx ? shared.cone : shared.ok);
|
|
|
|
}
|
|
|
|
|
2015-07-26 09:20:46 -04:00
|
|
|
void msetCommand(client *c) {
|
2010-06-21 18:07:48 -04:00
|
|
|
msetGenericCommand(c,0);
|
|
|
|
}
|
|
|
|
|
2015-07-26 09:20:46 -04:00
|
|
|
void msetnxCommand(client *c) {
|
2010-06-21 18:07:48 -04:00
|
|
|
msetGenericCommand(c,1);
|
|
|
|
}
|
|
|
|
|
2015-07-26 09:20:46 -04:00
|
|
|
void incrDecrCommand(client *c, long long incr) {
|
2010-12-19 06:22:12 -05:00
|
|
|
long long value, oldvalue;
|
2011-06-14 09:34:27 -04:00
|
|
|
robj *o, *new;
|
2010-06-21 18:07:48 -04:00
|
|
|
|
|
|
|
o = lookupKeyWrite(c->db,c->argv[1]);
|
2015-07-26 09:28:00 -04:00
|
|
|
if (o != NULL && checkType(c,o,OBJ_STRING)) return;
|
2015-07-26 17:17:55 -04:00
|
|
|
if (getLongLongFromObjectOrReply(c,o,&value,NULL) != C_OK) return;
|
2010-06-21 18:07:48 -04:00
|
|
|
|
2010-12-19 06:22:12 -05:00
|
|
|
oldvalue = value;
|
2012-02-21 12:25:49 -05:00
|
|
|
if ((incr < 0 && oldvalue < 0 && incr < (LLONG_MIN-oldvalue)) ||
|
|
|
|
(incr > 0 && oldvalue > 0 && incr > (LLONG_MAX-oldvalue))) {
|
2010-12-19 06:22:12 -05:00
|
|
|
addReplyError(c,"increment or decrement would overflow");
|
|
|
|
return;
|
|
|
|
}
|
2012-02-21 12:25:49 -05:00
|
|
|
value += incr;
|
2014-10-03 06:53:57 -04:00
|
|
|
|
2015-07-26 09:28:00 -04:00
|
|
|
if (o && o->refcount == 1 && o->encoding == OBJ_ENCODING_INT &&
|
2015-07-27 03:41:48 -04:00
|
|
|
(value < 0 || value >= OBJ_SHARED_INTEGERS) &&
|
2014-10-03 06:53:57 -04:00
|
|
|
value >= LONG_MIN && value <= LONG_MAX)
|
|
|
|
{
|
|
|
|
new = o;
|
|
|
|
o->ptr = (void*)((long)value);
|
|
|
|
} else {
|
|
|
|
new = createStringObjectFromLongLong(value);
|
|
|
|
if (o) {
|
|
|
|
dbOverwrite(c->db,c->argv[1],new);
|
|
|
|
} else {
|
|
|
|
dbAdd(c->db,c->argv[1],new);
|
|
|
|
}
|
|
|
|
}
|
2010-12-29 13:39:42 -05:00
|
|
|
signalModifiedKey(c->db,c->argv[1]);
|
2015-07-27 03:41:48 -04:00
|
|
|
notifyKeyspaceEvent(NOTIFY_STRING,"incrby",c->argv[1],c->db->id);
|
2010-06-21 18:07:48 -04:00
|
|
|
server.dirty++;
|
|
|
|
addReply(c,shared.colon);
|
2011-06-14 09:34:27 -04:00
|
|
|
addReply(c,new);
|
2010-06-21 18:07:48 -04:00
|
|
|
addReply(c,shared.crlf);
|
|
|
|
}
|
|
|
|
|
2015-07-26 09:20:46 -04:00
|
|
|
void incrCommand(client *c) {
|
2010-06-21 18:07:48 -04:00
|
|
|
incrDecrCommand(c,1);
|
|
|
|
}
|
|
|
|
|
2015-07-26 09:20:46 -04:00
|
|
|
void decrCommand(client *c) {
|
2010-06-21 18:07:48 -04:00
|
|
|
incrDecrCommand(c,-1);
|
|
|
|
}
|
|
|
|
|
2015-07-26 09:20:46 -04:00
|
|
|
void incrbyCommand(client *c) {
|
2010-06-21 18:07:48 -04:00
|
|
|
long long incr;
|
|
|
|
|
2015-07-26 17:17:55 -04:00
|
|
|
if (getLongLongFromObjectOrReply(c, c->argv[2], &incr, NULL) != C_OK) return;
|
2010-06-21 18:07:48 -04:00
|
|
|
incrDecrCommand(c,incr);
|
|
|
|
}
|
|
|
|
|
2015-07-26 09:20:46 -04:00
|
|
|
void decrbyCommand(client *c) {
|
2010-06-21 18:07:48 -04:00
|
|
|
long long incr;
|
|
|
|
|
2015-07-26 17:17:55 -04:00
|
|
|
if (getLongLongFromObjectOrReply(c, c->argv[2], &incr, NULL) != C_OK) return;
|
2010-06-21 18:07:48 -04:00
|
|
|
incrDecrCommand(c,-incr);
|
|
|
|
}
|
|
|
|
|
2015-07-26 09:20:46 -04:00
|
|
|
void incrbyfloatCommand(client *c) {
|
2011-11-12 13:27:35 -05:00
|
|
|
long double incr, value;
|
2011-11-14 04:15:13 -05:00
|
|
|
robj *o, *new, *aux;
|
2011-11-12 13:27:35 -05:00
|
|
|
|
|
|
|
o = lookupKeyWrite(c->db,c->argv[1]);
|
2015-07-26 09:28:00 -04:00
|
|
|
if (o != NULL && checkType(c,o,OBJ_STRING)) return;
|
2015-07-26 17:17:55 -04:00
|
|
|
if (getLongDoubleFromObjectOrReply(c,o,&value,NULL) != C_OK ||
|
|
|
|
getLongDoubleFromObjectOrReply(c,c->argv[2],&incr,NULL) != C_OK)
|
2011-11-12 13:27:35 -05:00
|
|
|
return;
|
|
|
|
|
|
|
|
value += incr;
|
|
|
|
if (isnan(value) || isinf(value)) {
|
|
|
|
addReplyError(c,"increment would produce NaN or Infinity");
|
|
|
|
return;
|
|
|
|
}
|
2014-12-02 12:19:30 -05:00
|
|
|
new = createStringObjectFromLongDouble(value,1);
|
2011-11-12 13:27:35 -05:00
|
|
|
if (o)
|
|
|
|
dbOverwrite(c->db,c->argv[1],new);
|
|
|
|
else
|
|
|
|
dbAdd(c->db,c->argv[1],new);
|
|
|
|
signalModifiedKey(c->db,c->argv[1]);
|
2015-07-27 03:41:48 -04:00
|
|
|
notifyKeyspaceEvent(NOTIFY_STRING,"incrbyfloat",c->argv[1],c->db->id);
|
2011-11-12 13:27:35 -05:00
|
|
|
server.dirty++;
|
|
|
|
addReplyBulk(c,new);
|
2011-11-14 04:15:13 -05:00
|
|
|
|
|
|
|
/* Always replicate INCRBYFLOAT as a SET command with the final value
|
2013-01-19 07:46:14 -05:00
|
|
|
* in order to make sure that differences in float precision or formatting
|
2011-11-14 04:15:13 -05:00
|
|
|
* will not create differences in replicas or after an AOF restart. */
|
|
|
|
aux = createStringObject("SET",3);
|
|
|
|
rewriteClientCommandArgument(c,0,aux);
|
|
|
|
decrRefCount(aux);
|
|
|
|
rewriteClientCommandArgument(c,2,new);
|
2011-11-12 13:27:35 -05:00
|
|
|
}
|
|
|
|
|
2015-07-26 09:20:46 -04:00
|
|
|
void appendCommand(client *c) {
|
2010-06-21 18:07:48 -04:00
|
|
|
size_t totlen;
|
2010-12-09 11:16:10 -05:00
|
|
|
robj *o, *append;
|
2010-06-21 18:07:48 -04:00
|
|
|
|
|
|
|
o = lookupKeyWrite(c->db,c->argv[1]);
|
|
|
|
if (o == NULL) {
|
|
|
|
/* Create the key */
|
2010-12-15 05:40:36 -05:00
|
|
|
c->argv[2] = tryObjectEncoding(c->argv[2]);
|
|
|
|
dbAdd(c->db,c->argv[1],c->argv[2]);
|
2010-06-21 18:07:48 -04:00
|
|
|
incrRefCount(c->argv[2]);
|
|
|
|
totlen = stringObjectLen(c->argv[2]);
|
|
|
|
} else {
|
2010-12-15 05:40:36 -05:00
|
|
|
/* Key exists, check type */
|
2015-07-26 09:28:00 -04:00
|
|
|
if (checkType(c,o,OBJ_STRING))
|
2010-06-21 18:07:48 -04:00
|
|
|
return;
|
2010-12-09 11:16:10 -05:00
|
|
|
|
2010-12-15 05:40:36 -05:00
|
|
|
/* "append" is an argument, so always an sds */
|
|
|
|
append = c->argv[2];
|
|
|
|
totlen = stringObjectLen(o)+sdslen(append->ptr);
|
2015-07-26 17:17:55 -04:00
|
|
|
if (checkStringLength(c,totlen) != C_OK)
|
2010-12-09 11:16:10 -05:00
|
|
|
return;
|
|
|
|
|
|
|
|
/* Append the value */
|
2014-03-30 12:32:17 -04:00
|
|
|
o = dbUnshareStringValue(c->db,c->argv[1],o);
|
2010-12-09 11:16:10 -05:00
|
|
|
o->ptr = sdscatlen(o->ptr,append->ptr,sdslen(append->ptr));
|
2010-06-21 18:07:48 -04:00
|
|
|
totlen = sdslen(o->ptr);
|
|
|
|
}
|
2010-12-29 13:39:42 -05:00
|
|
|
signalModifiedKey(c->db,c->argv[1]);
|
2015-07-27 03:41:48 -04:00
|
|
|
notifyKeyspaceEvent(NOTIFY_STRING,"append",c->argv[1],c->db->id);
|
2010-06-21 18:07:48 -04:00
|
|
|
server.dirty++;
|
2010-09-02 08:30:56 -04:00
|
|
|
addReplyLongLong(c,totlen);
|
2010-06-21 18:07:48 -04:00
|
|
|
}
|
|
|
|
|
2015-07-26 09:20:46 -04:00
|
|
|
void strlenCommand(client *c) {
|
2010-07-27 04:09:26 -04:00
|
|
|
robj *o;
|
|
|
|
if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL ||
|
2015-07-26 09:28:00 -04:00
|
|
|
checkType(c,o,OBJ_STRING)) return;
|
2010-12-15 05:49:04 -05:00
|
|
|
addReplyLongLong(c,stringObjectLen(o));
|
2010-07-27 04:09:26 -04:00
|
|
|
}
|