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