xref: /redis-3.2.3/src/t_string.c (revision d4831e32)
1 /*
2  * Copyright (c) 2009-2012, Salvatore Sanfilippo <antirez at gmail dot com>
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions are met:
7  *
8  *   * Redistributions of source code must retain the above copyright notice,
9  *     this list of conditions and the following disclaimer.
10  *   * Redistributions in binary form must reproduce the above copyright
11  *     notice, this list of conditions and the following disclaimer in the
12  *     documentation and/or other materials provided with the distribution.
13  *   * Neither the name of Redis nor the names of its contributors may be used
14  *     to endorse or promote products derived from this software without
15  *     specific prior written permission.
16  *
17  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
18  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
21  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
22  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
25  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27  * POSSIBILITY OF SUCH DAMAGE.
28  */
29 
30 #include "server.h"
31 #include <math.h> /* isnan(), isinf() */
32 
33 /*-----------------------------------------------------------------------------
34  * String Commands
35  *----------------------------------------------------------------------------*/
36 
checkStringLength(client * c,long long size)37 static int checkStringLength(client *c, long long size) {
38     if (size > 512*1024*1024) {
39         addReplyError(c,"string exceeds maximum allowed size (512MB)");
40         return C_ERR;
41     }
42     return C_OK;
43 }
44 
45 /* The setGenericCommand() function implements the SET operation with different
46  * options and variants. This function is called in order to implement the
47  * following commands: SET, SETEX, PSETEX, SETNX.
48  *
49  * 'flags' changes the behavior of the command (NX or XX, see belove).
50  *
51  * 'expire' represents an expire to set in form of a Redis object as passed
52  * by the user. It is interpreted according to the specified 'unit'.
53  *
54  * 'ok_reply' and 'abort_reply' is what the function will reply to the client
55  * if the operation is performed, or when it is not because of NX or
56  * XX flags.
57  *
58  * If ok_reply is NULL "+OK" is used.
59  * If abort_reply is NULL, "$-1" is used. */
60 
61 #define OBJ_SET_NO_FLAGS 0
62 #define OBJ_SET_NX (1<<0)     /* Set if key not exists. */
63 #define OBJ_SET_XX (1<<1)     /* Set if key exists. */
64 #define OBJ_SET_EX (1<<2)     /* Set if time in seconds is given */
65 #define OBJ_SET_PX (1<<3)     /* Set if time in ms in given */
66 
setGenericCommand(client * c,int flags,robj * key,robj * val,robj * expire,int unit,robj * ok_reply,robj * abort_reply)67 void setGenericCommand(client *c, int flags, robj *key, robj *val, robj *expire, int unit, robj *ok_reply, robj *abort_reply) {
68     long long milliseconds = 0; /* initialized to avoid any harmness warning */
69 
70     if (expire) {
71         if (getLongLongFromObjectOrReply(c, expire, &milliseconds, NULL) != C_OK)
72             return;
73         if (milliseconds <= 0) {
74             addReplyErrorFormat(c,"invalid expire time in %s",c->cmd->name);
75             return;
76         }
77         if (unit == UNIT_SECONDS) milliseconds *= 1000;
78     }
79 
80     if ((flags & OBJ_SET_NX && lookupKeyWrite(c->db,key) != NULL) ||
81         (flags & OBJ_SET_XX && lookupKeyWrite(c->db,key) == NULL))
82     {
83         addReply(c, abort_reply ? abort_reply : shared.nullbulk);
84         return;
85     }
86     setKey(c->db,key,val);
87     server.dirty++;
88     if (expire) setExpire(c->db,key,mstime()+milliseconds);
89     notifyKeyspaceEvent(NOTIFY_STRING,"set",key,c->db->id);
90     if (expire) notifyKeyspaceEvent(NOTIFY_GENERIC,
91         "expire",key,c->db->id);
92     addReply(c, ok_reply ? ok_reply : shared.ok);
93 }
94 
95 /* SET key value [NX] [XX] [EX <seconds>] [PX <milliseconds>] */
setCommand(client * c)96 void setCommand(client *c) {
97     int j;
98     robj *expire = NULL;
99     int unit = UNIT_SECONDS;
100     int flags = OBJ_SET_NO_FLAGS;
101 
102     for (j = 3; j < c->argc; j++) {
103         char *a = c->argv[j]->ptr;
104         robj *next = (j == c->argc-1) ? NULL : c->argv[j+1];
105 
106         if ((a[0] == 'n' || a[0] == 'N') &&
107             (a[1] == 'x' || a[1] == 'X') && a[2] == '\0' &&
108             !(flags & OBJ_SET_XX))
109         {
110             flags |= OBJ_SET_NX;
111         } else if ((a[0] == 'x' || a[0] == 'X') &&
112                    (a[1] == 'x' || a[1] == 'X') && a[2] == '\0' &&
113                    !(flags & OBJ_SET_NX))
114         {
115             flags |= OBJ_SET_XX;
116         } else if ((a[0] == 'e' || a[0] == 'E') &&
117                    (a[1] == 'x' || a[1] == 'X') && a[2] == '\0' &&
118                    !(flags & OBJ_SET_PX) && next)
119         {
120             flags |= OBJ_SET_EX;
121             unit = UNIT_SECONDS;
122             expire = next;
123             j++;
124         } else if ((a[0] == 'p' || a[0] == 'P') &&
125                    (a[1] == 'x' || a[1] == 'X') && a[2] == '\0' &&
126                    !(flags & OBJ_SET_EX) && next)
127         {
128             flags |= OBJ_SET_PX;
129             unit = UNIT_MILLISECONDS;
130             expire = next;
131             j++;
132         } else {
133             addReply(c,shared.syntaxerr);
134             return;
135         }
136     }
137 
138     c->argv[2] = tryObjectEncoding(c->argv[2]);
139     setGenericCommand(c,flags,c->argv[1],c->argv[2],expire,unit,NULL,NULL);
140 }
141 
setnxCommand(client * c)142 void setnxCommand(client *c) {
143     c->argv[2] = tryObjectEncoding(c->argv[2]);
144     setGenericCommand(c,OBJ_SET_NX,c->argv[1],c->argv[2],NULL,0,shared.cone,shared.czero);
145 }
146 
setexCommand(client * c)147 void setexCommand(client *c) {
148     c->argv[3] = tryObjectEncoding(c->argv[3]);
149     setGenericCommand(c,OBJ_SET_NO_FLAGS,c->argv[1],c->argv[3],c->argv[2],UNIT_SECONDS,NULL,NULL);
150 }
151 
psetexCommand(client * c)152 void psetexCommand(client *c) {
153     c->argv[3] = tryObjectEncoding(c->argv[3]);
154     setGenericCommand(c,OBJ_SET_NO_FLAGS,c->argv[1],c->argv[3],c->argv[2],UNIT_MILLISECONDS,NULL,NULL);
155 }
156 
getGenericCommand(client * c)157 int getGenericCommand(client *c) {
158     robj *o;
159 
160     if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.nullbulk)) == NULL)
161         return C_OK;
162 
163     if (o->type != OBJ_STRING) {
164         addReply(c,shared.wrongtypeerr);
165         return C_ERR;
166     } else {
167         addReplyBulk(c,o);
168         return C_OK;
169     }
170 }
171 
getCommand(client * c)172 void getCommand(client *c) {
173     getGenericCommand(c);
174 }
175 
getsetCommand(client * c)176 void getsetCommand(client *c) {
177     if (getGenericCommand(c) == C_ERR) return;
178     c->argv[2] = tryObjectEncoding(c->argv[2]);
179     setKey(c->db,c->argv[1],c->argv[2]);
180     notifyKeyspaceEvent(NOTIFY_STRING,"set",c->argv[1],c->db->id);
181     server.dirty++;
182 }
183 
setrangeCommand(client * c)184 void setrangeCommand(client *c) {
185     robj *o;
186     long offset;
187     sds value = c->argv[3]->ptr;
188 
189     if (getLongFromObjectOrReply(c,c->argv[2],&offset,NULL) != C_OK)
190         return;
191 
192     if (offset < 0) {
193         addReplyError(c,"offset is out of range");
194         return;
195     }
196 
197     o = lookupKeyWrite(c->db,c->argv[1]);
198     if (o == NULL) {
199         /* Return 0 when setting nothing on a non-existing string */
200         if (sdslen(value) == 0) {
201             addReply(c,shared.czero);
202             return;
203         }
204 
205         /* Return when the resulting string exceeds allowed size */
206         if (checkStringLength(c,offset+sdslen(value)) != C_OK)
207             return;
208 
209         o = createObject(OBJ_STRING,sdsnewlen(NULL, offset+sdslen(value)));
210         dbAdd(c->db,c->argv[1],o);
211     } else {
212         size_t olen;
213 
214         /* Key exists, check type */
215         if (checkType(c,o,OBJ_STRING))
216             return;
217 
218         /* Return existing string length when setting nothing */
219         olen = stringObjectLen(o);
220         if (sdslen(value) == 0) {
221             addReplyLongLong(c,olen);
222             return;
223         }
224 
225         /* Return when the resulting string exceeds allowed size */
226         if (checkStringLength(c,offset+sdslen(value)) != C_OK)
227             return;
228 
229         /* Create a copy when the object is shared or encoded. */
230         o = dbUnshareStringValue(c->db,c->argv[1],o);
231     }
232 
233     if (sdslen(value) > 0) {
234         o->ptr = sdsgrowzero(o->ptr,offset+sdslen(value));
235         memcpy((char*)o->ptr+offset,value,sdslen(value));
236         signalModifiedKey(c->db,c->argv[1]);
237         notifyKeyspaceEvent(NOTIFY_STRING,
238             "setrange",c->argv[1],c->db->id);
239         server.dirty++;
240     }
241     addReplyLongLong(c,sdslen(o->ptr));
242 }
243 
getrangeCommand(client * c)244 void getrangeCommand(client *c) {
245     robj *o;
246     long long start, end;
247     char *str, llbuf[32];
248     size_t strlen;
249 
250     if (getLongLongFromObjectOrReply(c,c->argv[2],&start,NULL) != C_OK)
251         return;
252     if (getLongLongFromObjectOrReply(c,c->argv[3],&end,NULL) != C_OK)
253         return;
254     if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.emptybulk)) == NULL ||
255         checkType(c,o,OBJ_STRING)) return;
256 
257     if (o->encoding == OBJ_ENCODING_INT) {
258         str = llbuf;
259         strlen = ll2string(llbuf,sizeof(llbuf),(long)o->ptr);
260     } else {
261         str = o->ptr;
262         strlen = sdslen(str);
263     }
264 
265     /* Convert negative indexes */
266     if (start < 0 && end < 0 && start > end) {
267         addReply(c,shared.emptybulk);
268         return;
269     }
270     if (start < 0) start = strlen+start;
271     if (end < 0) end = strlen+end;
272     if (start < 0) start = 0;
273     if (end < 0) end = 0;
274     if ((unsigned long long)end >= strlen) end = strlen-1;
275 
276     /* Precondition: end >= 0 && end < strlen, so the only condition where
277      * nothing can be returned is: start > end. */
278     if (start > end || strlen == 0) {
279         addReply(c,shared.emptybulk);
280     } else {
281         addReplyBulkCBuffer(c,(char*)str+start,end-start+1);
282     }
283 }
284 
mgetCommand(client * c)285 void mgetCommand(client *c) {
286     int j;
287 
288     addReplyMultiBulkLen(c,c->argc-1);
289     for (j = 1; j < c->argc; j++) {
290         robj *o = lookupKeyRead(c->db,c->argv[j]);
291         if (o == NULL) {
292             addReply(c,shared.nullbulk);
293         } else {
294             if (o->type != OBJ_STRING) {
295                 addReply(c,shared.nullbulk);
296             } else {
297                 addReplyBulk(c,o);
298             }
299         }
300     }
301 }
302 
msetGenericCommand(client * c,int nx)303 void msetGenericCommand(client *c, int nx) {
304     int j, busykeys = 0;
305 
306     if ((c->argc % 2) == 0) {
307         addReplyError(c,"wrong number of arguments for MSET");
308         return;
309     }
310     /* Handle the NX flag. The MSETNX semantic is to return zero and don't
311      * set nothing at all if at least one already key exists. */
312     if (nx) {
313         for (j = 1; j < c->argc; j += 2) {
314             if (lookupKeyWrite(c->db,c->argv[j]) != NULL) {
315                 busykeys++;
316             }
317         }
318         if (busykeys) {
319             addReply(c, shared.czero);
320             return;
321         }
322     }
323 
324     for (j = 1; j < c->argc; j += 2) {
325         c->argv[j+1] = tryObjectEncoding(c->argv[j+1]);
326         setKey(c->db,c->argv[j],c->argv[j+1]);
327         notifyKeyspaceEvent(NOTIFY_STRING,"set",c->argv[j],c->db->id);
328     }
329     server.dirty += (c->argc-1)/2;
330     addReply(c, nx ? shared.cone : shared.ok);
331 }
332 
msetCommand(client * c)333 void msetCommand(client *c) {
334     msetGenericCommand(c,0);
335 }
336 
msetnxCommand(client * c)337 void msetnxCommand(client *c) {
338     msetGenericCommand(c,1);
339 }
340 
incrDecrCommand(client * c,long long incr)341 void incrDecrCommand(client *c, long long incr) {
342     long long value, oldvalue;
343     robj *o, *new;
344 
345     o = lookupKeyWrite(c->db,c->argv[1]);
346     if (o != NULL && checkType(c,o,OBJ_STRING)) return;
347     if (getLongLongFromObjectOrReply(c,o,&value,NULL) != C_OK) return;
348 
349     oldvalue = value;
350     if ((incr < 0 && oldvalue < 0 && incr < (LLONG_MIN-oldvalue)) ||
351         (incr > 0 && oldvalue > 0 && incr > (LLONG_MAX-oldvalue))) {
352         addReplyError(c,"increment or decrement would overflow");
353         return;
354     }
355     value += incr;
356 
357     if (o && o->refcount == 1 && o->encoding == OBJ_ENCODING_INT &&
358         (value < 0 || value >= OBJ_SHARED_INTEGERS) &&
359         value >= LONG_MIN && value <= LONG_MAX)
360     {
361         new = o;
362         o->ptr = (void*)((long)value);
363     } else {
364         new = createStringObjectFromLongLong(value);
365         if (o) {
366             dbOverwrite(c->db,c->argv[1],new);
367         } else {
368             dbAdd(c->db,c->argv[1],new);
369         }
370     }
371     signalModifiedKey(c->db,c->argv[1]);
372     notifyKeyspaceEvent(NOTIFY_STRING,"incrby",c->argv[1],c->db->id);
373     server.dirty++;
374     addReply(c,shared.colon);
375     addReply(c,new);
376     addReply(c,shared.crlf);
377 }
378 
incrCommand(client * c)379 void incrCommand(client *c) {
380     incrDecrCommand(c,1);
381 }
382 
decrCommand(client * c)383 void decrCommand(client *c) {
384     incrDecrCommand(c,-1);
385 }
386 
incrbyCommand(client * c)387 void incrbyCommand(client *c) {
388     long long incr;
389 
390     if (getLongLongFromObjectOrReply(c, c->argv[2], &incr, NULL) != C_OK) return;
391     incrDecrCommand(c,incr);
392 }
393 
decrbyCommand(client * c)394 void decrbyCommand(client *c) {
395     long long incr;
396 
397     if (getLongLongFromObjectOrReply(c, c->argv[2], &incr, NULL) != C_OK) return;
398     incrDecrCommand(c,-incr);
399 }
400 
incrbyfloatCommand(client * c)401 void incrbyfloatCommand(client *c) {
402     long double incr, value;
403     robj *o, *new, *aux;
404 
405     o = lookupKeyWrite(c->db,c->argv[1]);
406     if (o != NULL && checkType(c,o,OBJ_STRING)) return;
407     if (getLongDoubleFromObjectOrReply(c,o,&value,NULL) != C_OK ||
408         getLongDoubleFromObjectOrReply(c,c->argv[2],&incr,NULL) != C_OK)
409         return;
410 
411     value += incr;
412     if (isnan(value) || isinf(value)) {
413         addReplyError(c,"increment would produce NaN or Infinity");
414         return;
415     }
416     new = createStringObjectFromLongDouble(value,1);
417     if (o)
418         dbOverwrite(c->db,c->argv[1],new);
419     else
420         dbAdd(c->db,c->argv[1],new);
421     signalModifiedKey(c->db,c->argv[1]);
422     notifyKeyspaceEvent(NOTIFY_STRING,"incrbyfloat",c->argv[1],c->db->id);
423     server.dirty++;
424     addReplyBulk(c,new);
425 
426     /* Always replicate INCRBYFLOAT as a SET command with the final value
427      * in order to make sure that differences in float precision or formatting
428      * will not create differences in replicas or after an AOF restart. */
429     aux = createStringObject("SET",3);
430     rewriteClientCommandArgument(c,0,aux);
431     decrRefCount(aux);
432     rewriteClientCommandArgument(c,2,new);
433 }
434 
appendCommand(client * c)435 void appendCommand(client *c) {
436     size_t totlen;
437     robj *o, *append;
438 
439     o = lookupKeyWrite(c->db,c->argv[1]);
440     if (o == NULL) {
441         /* Create the key */
442         c->argv[2] = tryObjectEncoding(c->argv[2]);
443         dbAdd(c->db,c->argv[1],c->argv[2]);
444         incrRefCount(c->argv[2]);
445         totlen = stringObjectLen(c->argv[2]);
446     } else {
447         /* Key exists, check type */
448         if (checkType(c,o,OBJ_STRING))
449             return;
450 
451         /* "append" is an argument, so always an sds */
452         append = c->argv[2];
453         totlen = stringObjectLen(o)+sdslen(append->ptr);
454         if (checkStringLength(c,totlen) != C_OK)
455             return;
456 
457         /* Append the value */
458         o = dbUnshareStringValue(c->db,c->argv[1],o);
459         o->ptr = sdscatlen(o->ptr,append->ptr,sdslen(append->ptr));
460         totlen = sdslen(o->ptr);
461     }
462     signalModifiedKey(c->db,c->argv[1]);
463     notifyKeyspaceEvent(NOTIFY_STRING,"append",c->argv[1],c->db->id);
464     server.dirty++;
465     addReplyLongLong(c,totlen);
466 }
467 
strlenCommand(client * c)468 void strlenCommand(client *c) {
469     robj *o;
470     if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL ||
471         checkType(c,o,OBJ_STRING)) return;
472     addReplyLongLong(c,stringObjectLen(o));
473 }
474