xref: /redis-3.2.3/src/t_string.c (revision 09cb281b)
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             addReplyErrorFormat(c,"invalid expire time in %s",c->cmd->name);
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         o = dbUnshareStringValue(c->db,c->argv[1],o);
219     }
220 
221     if (sdslen(value) > 0) {
222         o->ptr = sdsgrowzero(o->ptr,offset+sdslen(value));
223         memcpy((char*)o->ptr+offset,value,sdslen(value));
224         signalModifiedKey(c->db,c->argv[1]);
225         notifyKeyspaceEvent(REDIS_NOTIFY_STRING,
226             "setrange",c->argv[1],c->db->id);
227         server.dirty++;
228     }
229     addReplyLongLong(c,sdslen(o->ptr));
230 }
231 
232 void getrangeCommand(redisClient *c) {
233     robj *o;
234     long long start, end;
235     char *str, llbuf[32];
236     size_t strlen;
237 
238     if (getLongLongFromObjectOrReply(c,c->argv[2],&start,NULL) != REDIS_OK)
239         return;
240     if (getLongLongFromObjectOrReply(c,c->argv[3],&end,NULL) != REDIS_OK)
241         return;
242     if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.emptybulk)) == NULL ||
243         checkType(c,o,REDIS_STRING)) return;
244 
245     if (o->encoding == REDIS_ENCODING_INT) {
246         str = llbuf;
247         strlen = ll2string(llbuf,sizeof(llbuf),(long)o->ptr);
248     } else {
249         str = o->ptr;
250         strlen = sdslen(str);
251     }
252 
253     /* Convert negative indexes */
254     if (start < 0) start = strlen+start;
255     if (end < 0) end = strlen+end;
256     if (start < 0) start = 0;
257     if (end < 0) end = 0;
258     if ((unsigned long long)end >= strlen) end = strlen-1;
259 
260     /* Precondition: end >= 0 && end < strlen, so the only condition where
261      * nothing can be returned is: start > end. */
262     if (start > end || strlen == 0) {
263         addReply(c,shared.emptybulk);
264     } else {
265         addReplyBulkCBuffer(c,(char*)str+start,end-start+1);
266     }
267 }
268 
269 void mgetCommand(redisClient *c) {
270     int j;
271 
272     addReplyMultiBulkLen(c,c->argc-1);
273     for (j = 1; j < c->argc; j++) {
274         robj *o = lookupKeyRead(c->db,c->argv[j]);
275         if (o == NULL) {
276             addReply(c,shared.nullbulk);
277         } else {
278             if (o->type != REDIS_STRING) {
279                 addReply(c,shared.nullbulk);
280             } else {
281                 addReplyBulk(c,o);
282             }
283         }
284     }
285 }
286 
287 void msetGenericCommand(redisClient *c, int nx) {
288     int j, busykeys = 0;
289 
290     if ((c->argc % 2) == 0) {
291         addReplyError(c,"wrong number of arguments for MSET");
292         return;
293     }
294     /* Handle the NX flag. The MSETNX semantic is to return zero and don't
295      * set nothing at all if at least one already key exists. */
296     if (nx) {
297         for (j = 1; j < c->argc; j += 2) {
298             if (lookupKeyWrite(c->db,c->argv[j]) != NULL) {
299                 busykeys++;
300             }
301         }
302         if (busykeys) {
303             addReply(c, shared.czero);
304             return;
305         }
306     }
307 
308     for (j = 1; j < c->argc; j += 2) {
309         c->argv[j+1] = tryObjectEncoding(c->argv[j+1]);
310         setKey(c->db,c->argv[j],c->argv[j+1]);
311         notifyKeyspaceEvent(REDIS_NOTIFY_STRING,"set",c->argv[j],c->db->id);
312     }
313     server.dirty += (c->argc-1)/2;
314     addReply(c, nx ? shared.cone : shared.ok);
315 }
316 
317 void msetCommand(redisClient *c) {
318     msetGenericCommand(c,0);
319 }
320 
321 void msetnxCommand(redisClient *c) {
322     msetGenericCommand(c,1);
323 }
324 
325 void incrDecrCommand(redisClient *c, long long incr) {
326     long long value, oldvalue;
327     robj *o, *new;
328 
329     o = lookupKeyWrite(c->db,c->argv[1]);
330     if (o != NULL && checkType(c,o,REDIS_STRING)) return;
331     if (getLongLongFromObjectOrReply(c,o,&value,NULL) != REDIS_OK) return;
332 
333     oldvalue = value;
334     if ((incr < 0 && oldvalue < 0 && incr < (LLONG_MIN-oldvalue)) ||
335         (incr > 0 && oldvalue > 0 && incr > (LLONG_MAX-oldvalue))) {
336         addReplyError(c,"increment or decrement would overflow");
337         return;
338     }
339     value += incr;
340     new = createStringObjectFromLongLong(value);
341     if (o)
342         dbOverwrite(c->db,c->argv[1],new);
343     else
344         dbAdd(c->db,c->argv[1],new);
345     signalModifiedKey(c->db,c->argv[1]);
346     notifyKeyspaceEvent(REDIS_NOTIFY_STRING,"incrby",c->argv[1],c->db->id);
347     server.dirty++;
348     addReply(c,shared.colon);
349     addReply(c,new);
350     addReply(c,shared.crlf);
351 }
352 
353 void incrCommand(redisClient *c) {
354     incrDecrCommand(c,1);
355 }
356 
357 void decrCommand(redisClient *c) {
358     incrDecrCommand(c,-1);
359 }
360 
361 void incrbyCommand(redisClient *c) {
362     long long incr;
363 
364     if (getLongLongFromObjectOrReply(c, c->argv[2], &incr, NULL) != REDIS_OK) return;
365     incrDecrCommand(c,incr);
366 }
367 
368 void decrbyCommand(redisClient *c) {
369     long long incr;
370 
371     if (getLongLongFromObjectOrReply(c, c->argv[2], &incr, NULL) != REDIS_OK) return;
372     incrDecrCommand(c,-incr);
373 }
374 
375 void incrbyfloatCommand(redisClient *c) {
376     long double incr, value;
377     robj *o, *new, *aux;
378 
379     o = lookupKeyWrite(c->db,c->argv[1]);
380     if (o != NULL && checkType(c,o,REDIS_STRING)) return;
381     if (getLongDoubleFromObjectOrReply(c,o,&value,NULL) != REDIS_OK ||
382         getLongDoubleFromObjectOrReply(c,c->argv[2],&incr,NULL) != REDIS_OK)
383         return;
384 
385     value += incr;
386     if (isnan(value) || isinf(value)) {
387         addReplyError(c,"increment would produce NaN or Infinity");
388         return;
389     }
390     new = createStringObjectFromLongDouble(value);
391     if (o)
392         dbOverwrite(c->db,c->argv[1],new);
393     else
394         dbAdd(c->db,c->argv[1],new);
395     signalModifiedKey(c->db,c->argv[1]);
396     notifyKeyspaceEvent(REDIS_NOTIFY_STRING,"incrbyfloat",c->argv[1],c->db->id);
397     server.dirty++;
398     addReplyBulk(c,new);
399 
400     /* Always replicate INCRBYFLOAT as a SET command with the final value
401      * in order to make sure that differences in float precision or formatting
402      * will not create differences in replicas or after an AOF restart. */
403     aux = createStringObject("SET",3);
404     rewriteClientCommandArgument(c,0,aux);
405     decrRefCount(aux);
406     rewriteClientCommandArgument(c,2,new);
407 }
408 
409 void appendCommand(redisClient *c) {
410     size_t totlen;
411     robj *o, *append;
412 
413     o = lookupKeyWrite(c->db,c->argv[1]);
414     if (o == NULL) {
415         /* Create the key */
416         c->argv[2] = tryObjectEncoding(c->argv[2]);
417         dbAdd(c->db,c->argv[1],c->argv[2]);
418         incrRefCount(c->argv[2]);
419         totlen = stringObjectLen(c->argv[2]);
420     } else {
421         /* Key exists, check type */
422         if (checkType(c,o,REDIS_STRING))
423             return;
424 
425         /* "append" is an argument, so always an sds */
426         append = c->argv[2];
427         totlen = stringObjectLen(o)+sdslen(append->ptr);
428         if (checkStringLength(c,totlen) != REDIS_OK)
429             return;
430 
431         /* Append the value */
432         o = dbUnshareStringValue(c->db,c->argv[1],o);
433         o->ptr = sdscatlen(o->ptr,append->ptr,sdslen(append->ptr));
434         totlen = sdslen(o->ptr);
435     }
436     signalModifiedKey(c->db,c->argv[1]);
437     notifyKeyspaceEvent(REDIS_NOTIFY_STRING,"append",c->argv[1],c->db->id);
438     server.dirty++;
439     addReplyLongLong(c,totlen);
440 }
441 
442 void strlenCommand(redisClient *c) {
443     robj *o;
444     if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL ||
445         checkType(c,o,REDIS_STRING)) return;
446     addReplyLongLong(c,stringObjectLen(o));
447 }
448