xref: /redis-3.2.3/src/t_string.c (revision ace86cfa)
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 
65 void setGenericCommand(redisClient *c, int flags, robj *key, robj *val, robj *expire, int unit, robj *ok_reply, robj *abort_reply) {
66     long long milliseconds = 0; /* initialized to avoid any harmness warning */
67 
68     if (expire) {
69         if (getLongLongFromObjectOrReply(c, expire, &milliseconds, NULL) != REDIS_OK)
70             return;
71         if (milliseconds <= 0) {
72             addReplyError(c,"invalid expire time in SETEX");
73             return;
74         }
75         if (unit == UNIT_SECONDS) milliseconds *= 1000;
76     }
77 
78     if ((flags & REDIS_SET_NX && lookupKeyWrite(c->db,key) != NULL) ||
79         (flags & REDIS_SET_XX && lookupKeyWrite(c->db,key) == NULL))
80     {
81         addReply(c, abort_reply ? abort_reply : shared.nullbulk);
82         return;
83     }
84     setKey(c->db,key,val);
85     server.dirty++;
86     if (expire) setExpire(c->db,key,mstime()+milliseconds);
87     notifyKeyspaceEvent(REDIS_NOTIFY_STRING,"set",key,c->db->id);
88     if (expire) notifyKeyspaceEvent(REDIS_NOTIFY_GENERIC,
89         "expire",key,c->db->id);
90     addReply(c, ok_reply ? ok_reply : shared.ok);
91 }
92 
93 /* SET key value [NX] [XX] [EX <seconds>] [PX <milliseconds>] */
94 void setCommand(redisClient *c) {
95     int j;
96     robj *expire = NULL;
97     int unit = UNIT_SECONDS;
98     int flags = REDIS_SET_NO_FLAGS;
99 
100     for (j = 3; j < c->argc; j++) {
101         char *a = c->argv[j]->ptr;
102         robj *next = (j == c->argc-1) ? NULL : c->argv[j+1];
103 
104         if ((a[0] == 'n' || a[0] == 'N') &&
105             (a[1] == 'x' || a[1] == 'X') && a[2] == '\0') {
106             flags |= REDIS_SET_NX;
107         } else if ((a[0] == 'x' || a[0] == 'X') &&
108                    (a[1] == 'x' || a[1] == 'X') && a[2] == '\0') {
109             flags |= REDIS_SET_XX;
110         } else if ((a[0] == 'e' || a[0] == 'E') &&
111                    (a[1] == 'x' || a[1] == 'X') && a[2] == '\0' && next) {
112             unit = UNIT_SECONDS;
113             expire = next;
114             j++;
115         } else if ((a[0] == 'p' || a[0] == 'P') &&
116                    (a[1] == 'x' || a[1] == 'X') && a[2] == '\0' && next) {
117             unit = UNIT_MILLISECONDS;
118             expire = next;
119             j++;
120         } else {
121             addReply(c,shared.syntaxerr);
122             return;
123         }
124     }
125 
126     c->argv[2] = tryObjectEncoding(c->argv[2]);
127     setGenericCommand(c,flags,c->argv[1],c->argv[2],expire,unit,NULL,NULL);
128 }
129 
130 void setnxCommand(redisClient *c) {
131     c->argv[2] = tryObjectEncoding(c->argv[2]);
132     setGenericCommand(c,REDIS_SET_NX,c->argv[1],c->argv[2],NULL,0,shared.cone,shared.czero);
133 }
134 
135 void setexCommand(redisClient *c) {
136     c->argv[3] = tryObjectEncoding(c->argv[3]);
137     setGenericCommand(c,REDIS_SET_NO_FLAGS,c->argv[1],c->argv[3],c->argv[2],UNIT_SECONDS,NULL,NULL);
138 }
139 
140 void psetexCommand(redisClient *c) {
141     c->argv[3] = tryObjectEncoding(c->argv[3]);
142     setGenericCommand(c,REDIS_SET_NO_FLAGS,c->argv[1],c->argv[3],c->argv[2],UNIT_MILLISECONDS,NULL,NULL);
143 }
144 
145 int getGenericCommand(redisClient *c) {
146     robj *o;
147 
148     if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.nullbulk)) == NULL)
149         return REDIS_OK;
150 
151     if (o->type != REDIS_STRING) {
152         addReply(c,shared.wrongtypeerr);
153         return REDIS_ERR;
154     } else {
155         addReplyBulk(c,o);
156         return REDIS_OK;
157     }
158 }
159 
160 void getCommand(redisClient *c) {
161     getGenericCommand(c);
162 }
163 
164 void getsetCommand(redisClient *c) {
165     if (getGenericCommand(c) == REDIS_ERR) return;
166     c->argv[2] = tryObjectEncoding(c->argv[2]);
167     setKey(c->db,c->argv[1],c->argv[2]);
168     notifyKeyspaceEvent(REDIS_NOTIFY_STRING,"set",c->argv[1],c->db->id);
169     server.dirty++;
170 }
171 
172 void setrangeCommand(redisClient *c) {
173     robj *o;
174     long offset;
175     sds value = c->argv[3]->ptr;
176 
177     if (getLongFromObjectOrReply(c,c->argv[2],&offset,NULL) != REDIS_OK)
178         return;
179 
180     if (offset < 0) {
181         addReplyError(c,"offset is out of range");
182         return;
183     }
184 
185     o = lookupKeyWrite(c->db,c->argv[1]);
186     if (o == NULL) {
187         /* Return 0 when setting nothing on a non-existing string */
188         if (sdslen(value) == 0) {
189             addReply(c,shared.czero);
190             return;
191         }
192 
193         /* Return when the resulting string exceeds allowed size */
194         if (checkStringLength(c,offset+sdslen(value)) != REDIS_OK)
195             return;
196 
197         o = createObject(REDIS_STRING,sdsempty());
198         dbAdd(c->db,c->argv[1],o);
199     } else {
200         size_t olen;
201 
202         /* Key exists, check type */
203         if (checkType(c,o,REDIS_STRING))
204             return;
205 
206         /* Return existing string length when setting nothing */
207         olen = stringObjectLen(o);
208         if (sdslen(value) == 0) {
209             addReplyLongLong(c,olen);
210             return;
211         }
212 
213         /* Return when the resulting string exceeds allowed size */
214         if (checkStringLength(c,offset+sdslen(value)) != REDIS_OK)
215             return;
216 
217         /* Create a copy when the object is shared or encoded. */
218         if (o->refcount != 1 || o->encoding != REDIS_ENCODING_RAW) {
219             robj *decoded = getDecodedObject(o);
220             o = createStringObject(decoded->ptr, sdslen(decoded->ptr));
221             decrRefCount(decoded);
222             dbOverwrite(c->db,c->argv[1],o);
223         }
224     }
225 
226     if (sdslen(value) > 0) {
227         o->ptr = sdsgrowzero(o->ptr,offset+sdslen(value));
228         memcpy((char*)o->ptr+offset,value,sdslen(value));
229         signalModifiedKey(c->db,c->argv[1]);
230         notifyKeyspaceEvent(REDIS_NOTIFY_STRING,
231             "setrange",c->argv[1],c->db->id);
232         server.dirty++;
233     }
234     addReplyLongLong(c,sdslen(o->ptr));
235 }
236 
237 void getrangeCommand(redisClient *c) {
238     robj *o;
239     long start, end;
240     char *str, llbuf[32];
241     size_t strlen;
242 
243     if (getLongFromObjectOrReply(c,c->argv[2],&start,NULL) != REDIS_OK)
244         return;
245     if (getLongFromObjectOrReply(c,c->argv[3],&end,NULL) != REDIS_OK)
246         return;
247     if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.emptybulk)) == NULL ||
248         checkType(c,o,REDIS_STRING)) return;
249 
250     if (o->encoding == REDIS_ENCODING_INT) {
251         str = llbuf;
252         strlen = ll2string(llbuf,sizeof(llbuf),(long)o->ptr);
253     } else {
254         str = o->ptr;
255         strlen = sdslen(str);
256     }
257 
258     /* Convert negative indexes */
259     if (start < 0) start = strlen+start;
260     if (end < 0) end = strlen+end;
261     if (start < 0) start = 0;
262     if (end < 0) end = 0;
263     if ((unsigned)end >= strlen) end = strlen-1;
264 
265     /* Precondition: end >= 0 && end < strlen, so the only condition where
266      * nothing can be returned is: start > end. */
267     if (start > end) {
268         addReply(c,shared.emptybulk);
269     } else {
270         addReplyBulkCBuffer(c,(char*)str+start,end-start+1);
271     }
272 }
273 
274 void mgetCommand(redisClient *c) {
275     int j;
276 
277     addReplyMultiBulkLen(c,c->argc-1);
278     for (j = 1; j < c->argc; j++) {
279         robj *o = lookupKeyRead(c->db,c->argv[j]);
280         if (o == NULL) {
281             addReply(c,shared.nullbulk);
282         } else {
283             if (o->type != REDIS_STRING) {
284                 addReply(c,shared.nullbulk);
285             } else {
286                 addReplyBulk(c,o);
287             }
288         }
289     }
290 }
291 
292 void msetGenericCommand(redisClient *c, int nx) {
293     int j, busykeys = 0;
294 
295     if ((c->argc % 2) == 0) {
296         addReplyError(c,"wrong number of arguments for MSET");
297         return;
298     }
299     /* Handle the NX flag. The MSETNX semantic is to return zero and don't
300      * set nothing at all if at least one already key exists. */
301     if (nx) {
302         for (j = 1; j < c->argc; j += 2) {
303             if (lookupKeyWrite(c->db,c->argv[j]) != NULL) {
304                 busykeys++;
305             }
306         }
307         if (busykeys) {
308             addReply(c, shared.czero);
309             return;
310         }
311     }
312 
313     for (j = 1; j < c->argc; j += 2) {
314         c->argv[j+1] = tryObjectEncoding(c->argv[j+1]);
315         setKey(c->db,c->argv[j],c->argv[j+1]);
316         notifyKeyspaceEvent(REDIS_NOTIFY_STRING,"set",c->argv[j],c->db->id);
317     }
318     server.dirty += (c->argc-1)/2;
319     addReply(c, nx ? shared.cone : shared.ok);
320 }
321 
322 void msetCommand(redisClient *c) {
323     msetGenericCommand(c,0);
324 }
325 
326 void msetnxCommand(redisClient *c) {
327     msetGenericCommand(c,1);
328 }
329 
330 void incrDecrCommand(redisClient *c, long long incr) {
331     long long value, oldvalue;
332     robj *o, *new;
333 
334     o = lookupKeyWrite(c->db,c->argv[1]);
335     if (o != NULL && checkType(c,o,REDIS_STRING)) return;
336     if (getLongLongFromObjectOrReply(c,o,&value,NULL) != REDIS_OK) return;
337 
338     oldvalue = value;
339     if ((incr < 0 && oldvalue < 0 && incr < (LLONG_MIN-oldvalue)) ||
340         (incr > 0 && oldvalue > 0 && incr > (LLONG_MAX-oldvalue))) {
341         addReplyError(c,"increment or decrement would overflow");
342         return;
343     }
344     value += incr;
345     new = createStringObjectFromLongLong(value);
346     if (o)
347         dbOverwrite(c->db,c->argv[1],new);
348     else
349         dbAdd(c->db,c->argv[1],new);
350     signalModifiedKey(c->db,c->argv[1]);
351     notifyKeyspaceEvent(REDIS_NOTIFY_STRING,"incrby",c->argv[1],c->db->id);
352     server.dirty++;
353     addReply(c,shared.colon);
354     addReply(c,new);
355     addReply(c,shared.crlf);
356 }
357 
358 void incrCommand(redisClient *c) {
359     incrDecrCommand(c,1);
360 }
361 
362 void decrCommand(redisClient *c) {
363     incrDecrCommand(c,-1);
364 }
365 
366 void incrbyCommand(redisClient *c) {
367     long long incr;
368 
369     if (getLongLongFromObjectOrReply(c, c->argv[2], &incr, NULL) != REDIS_OK) return;
370     incrDecrCommand(c,incr);
371 }
372 
373 void decrbyCommand(redisClient *c) {
374     long long incr;
375 
376     if (getLongLongFromObjectOrReply(c, c->argv[2], &incr, NULL) != REDIS_OK) return;
377     incrDecrCommand(c,-incr);
378 }
379 
380 void incrbyfloatCommand(redisClient *c) {
381     long double incr, value;
382     robj *o, *new, *aux;
383 
384     o = lookupKeyWrite(c->db,c->argv[1]);
385     if (o != NULL && checkType(c,o,REDIS_STRING)) return;
386     if (getLongDoubleFromObjectOrReply(c,o,&value,NULL) != REDIS_OK ||
387         getLongDoubleFromObjectOrReply(c,c->argv[2],&incr,NULL) != REDIS_OK)
388         return;
389 
390     value += incr;
391     if (isnan(value) || isinf(value)) {
392         addReplyError(c,"increment would produce NaN or Infinity");
393         return;
394     }
395     new = createStringObjectFromLongDouble(value);
396     if (o)
397         dbOverwrite(c->db,c->argv[1],new);
398     else
399         dbAdd(c->db,c->argv[1],new);
400     signalModifiedKey(c->db,c->argv[1]);
401     notifyKeyspaceEvent(REDIS_NOTIFY_STRING,"incrbyfloat",c->argv[1],c->db->id);
402     server.dirty++;
403     addReplyBulk(c,new);
404 
405     /* Always replicate INCRBYFLOAT as a SET command with the final value
406      * in order to make sure that differences in float precision or formatting
407      * will not create differences in replicas or after an AOF restart. */
408     aux = createStringObject("SET",3);
409     rewriteClientCommandArgument(c,0,aux);
410     decrRefCount(aux);
411     rewriteClientCommandArgument(c,2,new);
412 }
413 
414 void appendCommand(redisClient *c) {
415     size_t totlen;
416     robj *o, *append;
417 
418     o = lookupKeyWrite(c->db,c->argv[1]);
419     if (o == NULL) {
420         /* Create the key */
421         c->argv[2] = tryObjectEncoding(c->argv[2]);
422         dbAdd(c->db,c->argv[1],c->argv[2]);
423         incrRefCount(c->argv[2]);
424         totlen = stringObjectLen(c->argv[2]);
425     } else {
426         /* Key exists, check type */
427         if (checkType(c,o,REDIS_STRING))
428             return;
429 
430         /* "append" is an argument, so always an sds */
431         append = c->argv[2];
432         totlen = stringObjectLen(o)+sdslen(append->ptr);
433         if (checkStringLength(c,totlen) != REDIS_OK)
434             return;
435 
436         /* If the object is shared or encoded, we have to make a copy */
437         if (o->refcount != 1 || o->encoding != REDIS_ENCODING_RAW) {
438             robj *decoded = getDecodedObject(o);
439             o = createStringObject(decoded->ptr, sdslen(decoded->ptr));
440             decrRefCount(decoded);
441             dbOverwrite(c->db,c->argv[1],o);
442         }
443 
444         /* Append the value */
445         o->ptr = sdscatlen(o->ptr,append->ptr,sdslen(append->ptr));
446         totlen = sdslen(o->ptr);
447     }
448     signalModifiedKey(c->db,c->argv[1]);
449     notifyKeyspaceEvent(REDIS_NOTIFY_STRING,"append",c->argv[1],c->db->id);
450     server.dirty++;
451     addReplyLongLong(c,totlen);
452 }
453 
454 void strlenCommand(redisClient *c) {
455     robj *o;
456     if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL ||
457         checkType(c,o,REDIS_STRING)) return;
458     addReplyLongLong(c,stringObjectLen(o));
459 }
460