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