xref: /redis-3.2.3/src/t_string.c (revision 2f75bbab)
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     value += incr;
348     if ((incr < 0 && value > oldvalue) || (incr > 0 && value < oldvalue)) {
349         addReplyError(c,"increment or decrement would overflow");
350         return;
351     }
352     new = createStringObjectFromLongLong(value);
353     if (o)
354         dbOverwrite(c->db,c->argv[1],new);
355     else
356         dbAdd(c->db,c->argv[1],new);
357     signalModifiedKey(c->db,c->argv[1]);
358     server.dirty++;
359     addReply(c,shared.colon);
360     addReply(c,new);
361     addReply(c,shared.crlf);
362 }
363 
364 void incrCommand(redisClient *c) {
365     incrDecrCommand(c,1);
366 }
367 
368 void decrCommand(redisClient *c) {
369     incrDecrCommand(c,-1);
370 }
371 
372 void incrbyCommand(redisClient *c) {
373     long long incr;
374 
375     if (getLongLongFromObjectOrReply(c, c->argv[2], &incr, NULL) != REDIS_OK) return;
376     incrDecrCommand(c,incr);
377 }
378 
379 void decrbyCommand(redisClient *c) {
380     long long incr;
381 
382     if (getLongLongFromObjectOrReply(c, c->argv[2], &incr, NULL) != REDIS_OK) return;
383     incrDecrCommand(c,-incr);
384 }
385 
386 void incrbyfloatCommand(redisClient *c) {
387     long double incr, value;
388     robj *o, *new, *aux;
389 
390     o = lookupKeyWrite(c->db,c->argv[1]);
391     if (o != NULL && checkType(c,o,REDIS_STRING)) return;
392     if (getLongDoubleFromObjectOrReply(c,o,&value,NULL) != REDIS_OK ||
393         getLongDoubleFromObjectOrReply(c,c->argv[2],&incr,NULL) != REDIS_OK)
394         return;
395 
396     value += incr;
397     if (isnan(value) || isinf(value)) {
398         addReplyError(c,"increment would produce NaN or Infinity");
399         return;
400     }
401     new = createStringObjectFromLongDouble(value);
402     if (o)
403         dbOverwrite(c->db,c->argv[1],new);
404     else
405         dbAdd(c->db,c->argv[1],new);
406     signalModifiedKey(c->db,c->argv[1]);
407     server.dirty++;
408     addReplyBulk(c,new);
409 
410     /* Always replicate INCRBYFLOAT as a SET command with the final value
411      * in order to make sure that differences in float pricision or formatting
412      * will not create differences in replicas or after an AOF restart. */
413     aux = createStringObject("SET",3);
414     rewriteClientCommandArgument(c,0,aux);
415     decrRefCount(aux);
416     rewriteClientCommandArgument(c,2,new);
417 }
418 
419 void appendCommand(redisClient *c) {
420     size_t totlen;
421     robj *o, *append;
422 
423     o = lookupKeyWrite(c->db,c->argv[1]);
424     if (o == NULL) {
425         /* Create the key */
426         c->argv[2] = tryObjectEncoding(c->argv[2]);
427         dbAdd(c->db,c->argv[1],c->argv[2]);
428         incrRefCount(c->argv[2]);
429         totlen = stringObjectLen(c->argv[2]);
430     } else {
431         /* Key exists, check type */
432         if (checkType(c,o,REDIS_STRING))
433             return;
434 
435         /* "append" is an argument, so always an sds */
436         append = c->argv[2];
437         totlen = stringObjectLen(o)+sdslen(append->ptr);
438         if (checkStringLength(c,totlen) != REDIS_OK)
439             return;
440 
441         /* If the object is shared or encoded, we have to make a copy */
442         if (o->refcount != 1 || o->encoding != REDIS_ENCODING_RAW) {
443             robj *decoded = getDecodedObject(o);
444             o = createStringObject(decoded->ptr, sdslen(decoded->ptr));
445             decrRefCount(decoded);
446             dbOverwrite(c->db,c->argv[1],o);
447         }
448 
449         /* Append the value */
450         o->ptr = sdscatlen(o->ptr,append->ptr,sdslen(append->ptr));
451         totlen = sdslen(o->ptr);
452     }
453     signalModifiedKey(c->db,c->argv[1]);
454     server.dirty++;
455     addReplyLongLong(c,totlen);
456 }
457 
458 void strlenCommand(redisClient *c) {
459     robj *o;
460     if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL ||
461         checkType(c,o,REDIS_STRING)) return;
462     addReplyLongLong(c,stringObjectLen(o));
463 }
464 
465