xref: /redis-3.2.3/src/t_string.c (revision dffbbb5a)
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 "redis.h"
31 #include <math.h> /* isnan(), isinf() */
32 
33 /*-----------------------------------------------------------------------------
34  * String Commands
35  *----------------------------------------------------------------------------*/
36 
37 static int checkStringLength(redisClient *c, long long size) {
38     if (size > 512*1024*1024) {
39         addReplyError(c,"string exceeds maximum allowed size (512MB)");
40         return REDIS_ERR;
41     }
42     return REDIS_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 REDIS_SET_NO_FLAGS 0
62 #define REDIS_SET_NX (1<<0)     /* Set if key not exists. */
63 #define REDIS_SET_XX (1<<1)     /* Set if key exists. */
64 #define REDIS_SET_EX (1<<2)     /* Set if time in seconds is given */
65 #define REDIS_SET_PX (1<<3)     /* Set if time in ms in given */
66 
67 void setGenericCommand(redisClient *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) != REDIS_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 & REDIS_SET_NX && lookupKeyWrite(c->db,key) != NULL) ||
81         (flags & REDIS_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(REDIS_NOTIFY_STRING,"set",key,c->db->id);
90     if (expire) notifyKeyspaceEvent(REDIS_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>] */
96 void setCommand(redisClient *c) {
97     int j;
98     robj *expire = NULL;
99     int unit = UNIT_SECONDS;
100     int flags = REDIS_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 & REDIS_SET_XX))
109         {
110             flags |= REDIS_SET_NX;
111         } else if ((a[0] == 'x' || a[0] == 'X') &&
112                    (a[1] == 'x' || a[1] == 'X') && a[2] == '\0' &&
113                    !(flags & REDIS_SET_NX))
114         {
115             flags |= REDIS_SET_XX;
116         } else if ((a[0] == 'e' || a[0] == 'E') &&
117                    (a[1] == 'x' || a[1] == 'X') && a[2] == '\0' &&
118                    !(flags & REDIS_SET_PX) && next)
119         {
120             flags |= REDIS_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 & REDIS_SET_EX) && next)
127         {
128             flags |= REDIS_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 
142 void setnxCommand(redisClient *c) {
143     c->argv[2] = tryObjectEncoding(c->argv[2]);
144     setGenericCommand(c,REDIS_SET_NX,c->argv[1],c->argv[2],NULL,0,shared.cone,shared.czero);
145 }
146 
147 void setexCommand(redisClient *c) {
148     c->argv[3] = tryObjectEncoding(c->argv[3]);
149     setGenericCommand(c,REDIS_SET_NO_FLAGS,c->argv[1],c->argv[3],c->argv[2],UNIT_SECONDS,NULL,NULL);
150 }
151 
152 void psetexCommand(redisClient *c) {
153     c->argv[3] = tryObjectEncoding(c->argv[3]);
154     setGenericCommand(c,REDIS_SET_NO_FLAGS,c->argv[1],c->argv[3],c->argv[2],UNIT_MILLISECONDS,NULL,NULL);
155 }
156 
157 int getGenericCommand(redisClient *c) {
158     robj *o;
159 
160     if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.nullbulk)) == NULL)
161         return REDIS_OK;
162 
163     if (o->type != REDIS_STRING) {
164         addReply(c,shared.wrongtypeerr);
165         return REDIS_ERR;
166     } else {
167         addReplyBulk(c,o);
168         return REDIS_OK;
169     }
170 }
171 
172 void getCommand(redisClient *c) {
173     getGenericCommand(c);
174 }
175 
176 void getsetCommand(redisClient *c) {
177     if (getGenericCommand(c) == REDIS_ERR) return;
178     c->argv[2] = tryObjectEncoding(c->argv[2]);
179     setKey(c->db,c->argv[1],c->argv[2]);
180     notifyKeyspaceEvent(REDIS_NOTIFY_STRING,"set",c->argv[1],c->db->id);
181     server.dirty++;
182 }
183 
184 void setrangeCommand(redisClient *c) {
185     robj *o;
186     long offset;
187     sds value = c->argv[3]->ptr;
188 
189     if (getLongFromObjectOrReply(c,c->argv[2],&offset,NULL) != REDIS_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)) != REDIS_OK)
207             return;
208 
209         o = createObject(REDIS_STRING,sdsempty());
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,REDIS_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)) != REDIS_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(REDIS_NOTIFY_STRING,
238             "setrange",c->argv[1],c->db->id);
239         server.dirty++;
240     }
241     addReplyLongLong(c,sdslen(o->ptr));
242 }
243 
244 void getrangeCommand(redisClient *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) != REDIS_OK)
251         return;
252     if (getLongLongFromObjectOrReply(c,c->argv[3],&end,NULL) != REDIS_OK)
253         return;
254     if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.emptybulk)) == NULL ||
255         checkType(c,o,REDIS_STRING)) return;
256 
257     if (o->encoding == REDIS_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) start = strlen+start;
267     if (end < 0) end = strlen+end;
268     if (start < 0) start = 0;
269     if (end < 0) end = 0;
270     if ((unsigned long long)end >= strlen) end = strlen-1;
271 
272     /* Precondition: end >= 0 && end < strlen, so the only condition where
273      * nothing can be returned is: start > end. */
274     if (start > end || strlen == 0) {
275         addReply(c,shared.emptybulk);
276     } else {
277         addReplyBulkCBuffer(c,(char*)str+start,end-start+1);
278     }
279 }
280 
281 void mgetCommand(redisClient *c) {
282     int j;
283 
284     addReplyMultiBulkLen(c,c->argc-1);
285     for (j = 1; j < c->argc; j++) {
286         robj *o = lookupKeyRead(c->db,c->argv[j]);
287         if (o == NULL) {
288             addReply(c,shared.nullbulk);
289         } else {
290             if (o->type != REDIS_STRING) {
291                 addReply(c,shared.nullbulk);
292             } else {
293                 addReplyBulk(c,o);
294             }
295         }
296     }
297 }
298 
299 void msetGenericCommand(redisClient *c, int nx) {
300     int j, busykeys = 0;
301 
302     if ((c->argc % 2) == 0) {
303         addReplyError(c,"wrong number of arguments for MSET");
304         return;
305     }
306     /* Handle the NX flag. The MSETNX semantic is to return zero and don't
307      * set nothing at all if at least one already key exists. */
308     if (nx) {
309         for (j = 1; j < c->argc; j += 2) {
310             if (lookupKeyWrite(c->db,c->argv[j]) != NULL) {
311                 busykeys++;
312             }
313         }
314         if (busykeys) {
315             addReply(c, shared.czero);
316             return;
317         }
318     }
319 
320     for (j = 1; j < c->argc; j += 2) {
321         c->argv[j+1] = tryObjectEncoding(c->argv[j+1]);
322         setKey(c->db,c->argv[j],c->argv[j+1]);
323         notifyKeyspaceEvent(REDIS_NOTIFY_STRING,"set",c->argv[j],c->db->id);
324     }
325     server.dirty += (c->argc-1)/2;
326     addReply(c, nx ? shared.cone : shared.ok);
327 }
328 
329 void msetCommand(redisClient *c) {
330     msetGenericCommand(c,0);
331 }
332 
333 void msetnxCommand(redisClient *c) {
334     msetGenericCommand(c,1);
335 }
336 
337 void incrDecrCommand(redisClient *c, long long incr) {
338     long long value, oldvalue;
339     robj *o, *new;
340 
341     o = lookupKeyWrite(c->db,c->argv[1]);
342     if (o != NULL && checkType(c,o,REDIS_STRING)) return;
343     if (getLongLongFromObjectOrReply(c,o,&value,NULL) != REDIS_OK) return;
344 
345     oldvalue = value;
346     if ((incr < 0 && oldvalue < 0 && incr < (LLONG_MIN-oldvalue)) ||
347         (incr > 0 && oldvalue > 0 && incr > (LLONG_MAX-oldvalue))) {
348         addReplyError(c,"increment or decrement would overflow");
349         return;
350     }
351     value += incr;
352 
353     if (o && o->refcount == 1 && o->encoding == REDIS_ENCODING_INT &&
354         (value < 0 || value >= REDIS_SHARED_INTEGERS) &&
355         value >= LONG_MIN && value <= LONG_MAX)
356     {
357         new = o;
358         o->ptr = (void*)((long)value);
359     } else {
360         new = createStringObjectFromLongLong(value);
361         if (o) {
362             dbOverwrite(c->db,c->argv[1],new);
363         } else {
364             dbAdd(c->db,c->argv[1],new);
365         }
366     }
367     signalModifiedKey(c->db,c->argv[1]);
368     notifyKeyspaceEvent(REDIS_NOTIFY_STRING,"incrby",c->argv[1],c->db->id);
369     server.dirty++;
370     addReply(c,shared.colon);
371     addReply(c,new);
372     addReply(c,shared.crlf);
373 }
374 
375 void incrCommand(redisClient *c) {
376     incrDecrCommand(c,1);
377 }
378 
379 void decrCommand(redisClient *c) {
380     incrDecrCommand(c,-1);
381 }
382 
383 void incrbyCommand(redisClient *c) {
384     long long incr;
385 
386     if (getLongLongFromObjectOrReply(c, c->argv[2], &incr, NULL) != REDIS_OK) return;
387     incrDecrCommand(c,incr);
388 }
389 
390 void decrbyCommand(redisClient *c) {
391     long long incr;
392 
393     if (getLongLongFromObjectOrReply(c, c->argv[2], &incr, NULL) != REDIS_OK) return;
394     incrDecrCommand(c,-incr);
395 }
396 
397 void incrbyfloatCommand(redisClient *c) {
398     long double incr, value;
399     robj *o, *new, *aux;
400 
401     o = lookupKeyWrite(c->db,c->argv[1]);
402     if (o != NULL && checkType(c,o,REDIS_STRING)) return;
403     if (getLongDoubleFromObjectOrReply(c,o,&value,NULL) != REDIS_OK ||
404         getLongDoubleFromObjectOrReply(c,c->argv[2],&incr,NULL) != REDIS_OK)
405         return;
406 
407     value += incr;
408     if (isnan(value) || isinf(value)) {
409         addReplyError(c,"increment would produce NaN or Infinity");
410         return;
411     }
412     new = createStringObjectFromLongDouble(value,1);
413     if (o)
414         dbOverwrite(c->db,c->argv[1],new);
415     else
416         dbAdd(c->db,c->argv[1],new);
417     signalModifiedKey(c->db,c->argv[1]);
418     notifyKeyspaceEvent(REDIS_NOTIFY_STRING,"incrbyfloat",c->argv[1],c->db->id);
419     server.dirty++;
420     addReplyBulk(c,new);
421 
422     /* Always replicate INCRBYFLOAT as a SET command with the final value
423      * in order to make sure that differences in float precision or formatting
424      * will not create differences in replicas or after an AOF restart. */
425     aux = createStringObject("SET",3);
426     rewriteClientCommandArgument(c,0,aux);
427     decrRefCount(aux);
428     rewriteClientCommandArgument(c,2,new);
429 }
430 
431 void appendCommand(redisClient *c) {
432     size_t totlen;
433     robj *o, *append;
434 
435     o = lookupKeyWrite(c->db,c->argv[1]);
436     if (o == NULL) {
437         /* Create the key */
438         c->argv[2] = tryObjectEncoding(c->argv[2]);
439         dbAdd(c->db,c->argv[1],c->argv[2]);
440         incrRefCount(c->argv[2]);
441         totlen = stringObjectLen(c->argv[2]);
442     } else {
443         /* Key exists, check type */
444         if (checkType(c,o,REDIS_STRING))
445             return;
446 
447         /* "append" is an argument, so always an sds */
448         append = c->argv[2];
449         totlen = stringObjectLen(o)+sdslen(append->ptr);
450         if (checkStringLength(c,totlen) != REDIS_OK)
451             return;
452 
453         /* Append the value */
454         o = dbUnshareStringValue(c->db,c->argv[1],o);
455         o->ptr = sdscatlen(o->ptr,append->ptr,sdslen(append->ptr));
456         totlen = sdslen(o->ptr);
457     }
458     signalModifiedKey(c->db,c->argv[1]);
459     notifyKeyspaceEvent(REDIS_NOTIFY_STRING,"append",c->argv[1],c->db->id);
460     server.dirty++;
461     addReplyLongLong(c,totlen);
462 }
463 
464 void strlenCommand(redisClient *c) {
465     robj *o;
466     if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL ||
467         checkType(c,o,REDIS_STRING)) return;
468     addReplyLongLong(c,stringObjectLen(o));
469 }
470