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