xref: /f-stack/app/redis-5.0.5/src/t_string.c (revision 572c4311)
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 "server.h"
31 #include <math.h> /* isnan(), isinf() */
32 
33 /*-----------------------------------------------------------------------------
34  * String Commands
35  *----------------------------------------------------------------------------*/
36 
checkStringLength(client * c,long long size)37 static int checkStringLength(client *c, long long size) {
38     if (size > 512*1024*1024) {
39         addReplyError(c,"string exceeds maximum allowed size (512MB)");
40         return C_ERR;
41     }
42     return C_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 OBJ_SET_NO_FLAGS 0
62 #define OBJ_SET_NX (1<<0)     /* Set if key not exists. */
63 #define OBJ_SET_XX (1<<1)     /* Set if key exists. */
64 #define OBJ_SET_EX (1<<2)     /* Set if time in seconds is given */
65 #define OBJ_SET_PX (1<<3)     /* Set if time in ms in given */
66 
setGenericCommand(client * c,int flags,robj * key,robj * val,robj * expire,int unit,robj * ok_reply,robj * abort_reply)67 void setGenericCommand(client *c, int flags, robj *key, robj *val, robj *expire, int unit, robj *ok_reply, robj *abort_reply) {
68     long long milliseconds = 0; /* initialized to avoid any harmness warning */
69 
70     if (expire) {
71         if (getLongLongFromObjectOrReply(c, expire, &milliseconds, NULL) != C_OK)
72             return;
73         if (milliseconds <= 0) {
74             addReplyErrorFormat(c,"invalid expire time in %s",c->cmd->name);
75             return;
76         }
77         if (unit == UNIT_SECONDS) milliseconds *= 1000;
78     }
79 
80     if ((flags & OBJ_SET_NX && lookupKeyWrite(c->db,key) != NULL) ||
81         (flags & OBJ_SET_XX && lookupKeyWrite(c->db,key) == NULL))
82     {
83         addReply(c, abort_reply ? abort_reply : shared.nullbulk);
84         return;
85     }
86     setKey(c->db,key,val);
87     server.dirty++;
88     if (expire) setExpire(c,c->db,key,mstime()+milliseconds);
89     notifyKeyspaceEvent(NOTIFY_STRING,"set",key,c->db->id);
90     if (expire) notifyKeyspaceEvent(NOTIFY_GENERIC,
91         "expire",key,c->db->id);
92     addReply(c, ok_reply ? ok_reply : shared.ok);
93 }
94 
95 /* SET key value [NX] [XX] [EX <seconds>] [PX <milliseconds>] */
setCommand(client * c)96 void setCommand(client *c) {
97     int j;
98     robj *expire = NULL;
99     int unit = UNIT_SECONDS;
100     int flags = OBJ_SET_NO_FLAGS;
101 
102     for (j = 3; j < c->argc; j++) {
103         char *a = c->argv[j]->ptr;
104         robj *next = (j == c->argc-1) ? NULL : c->argv[j+1];
105 
106         if ((a[0] == 'n' || a[0] == 'N') &&
107             (a[1] == 'x' || a[1] == 'X') && a[2] == '\0' &&
108             !(flags & OBJ_SET_XX))
109         {
110             flags |= OBJ_SET_NX;
111         } else if ((a[0] == 'x' || a[0] == 'X') &&
112                    (a[1] == 'x' || a[1] == 'X') && a[2] == '\0' &&
113                    !(flags & OBJ_SET_NX))
114         {
115             flags |= OBJ_SET_XX;
116         } else if ((a[0] == 'e' || a[0] == 'E') &&
117                    (a[1] == 'x' || a[1] == 'X') && a[2] == '\0' &&
118                    !(flags & OBJ_SET_PX) && next)
119         {
120             flags |= OBJ_SET_EX;
121             unit = UNIT_SECONDS;
122             expire = next;
123             j++;
124         } else if ((a[0] == 'p' || a[0] == 'P') &&
125                    (a[1] == 'x' || a[1] == 'X') && a[2] == '\0' &&
126                    !(flags & OBJ_SET_EX) && next)
127         {
128             flags |= OBJ_SET_PX;
129             unit = UNIT_MILLISECONDS;
130             expire = next;
131             j++;
132         } else {
133             addReply(c,shared.syntaxerr);
134             return;
135         }
136     }
137 
138     c->argv[2] = tryObjectEncoding(c->argv[2]);
139     setGenericCommand(c,flags,c->argv[1],c->argv[2],expire,unit,NULL,NULL);
140 }
141 
setnxCommand(client * c)142 void setnxCommand(client *c) {
143     c->argv[2] = tryObjectEncoding(c->argv[2]);
144     setGenericCommand(c,OBJ_SET_NX,c->argv[1],c->argv[2],NULL,0,shared.cone,shared.czero);
145 }
146 
setexCommand(client * c)147 void setexCommand(client *c) {
148     c->argv[3] = tryObjectEncoding(c->argv[3]);
149     setGenericCommand(c,OBJ_SET_NO_FLAGS,c->argv[1],c->argv[3],c->argv[2],UNIT_SECONDS,NULL,NULL);
150 }
151 
psetexCommand(client * c)152 void psetexCommand(client *c) {
153     c->argv[3] = tryObjectEncoding(c->argv[3]);
154     setGenericCommand(c,OBJ_SET_NO_FLAGS,c->argv[1],c->argv[3],c->argv[2],UNIT_MILLISECONDS,NULL,NULL);
155 }
156 
getGenericCommand(client * c)157 int getGenericCommand(client *c) {
158     robj *o;
159 
160     if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.nullbulk)) == NULL)
161         return C_OK;
162 
163     if (o->type != OBJ_STRING) {
164         addReply(c,shared.wrongtypeerr);
165         return C_ERR;
166     } else {
167         addReplyBulk(c,o);
168         return C_OK;
169     }
170 }
171 
getCommand(client * c)172 void getCommand(client *c) {
173     getGenericCommand(c);
174 }
175 
getsetCommand(client * c)176 void getsetCommand(client *c) {
177     if (getGenericCommand(c) == C_ERR) return;
178     c->argv[2] = tryObjectEncoding(c->argv[2]);
179     setKey(c->db,c->argv[1],c->argv[2]);
180     notifyKeyspaceEvent(NOTIFY_STRING,"set",c->argv[1],c->db->id);
181     server.dirty++;
182 }
183 
setrangeCommand(client * c)184 void setrangeCommand(client *c) {
185     robj *o;
186     long offset;
187     sds value = c->argv[3]->ptr;
188 
189     if (getLongFromObjectOrReply(c,c->argv[2],&offset,NULL) != C_OK)
190         return;
191 
192     if (offset < 0) {
193         addReplyError(c,"offset is out of range");
194         return;
195     }
196 
197     o = lookupKeyWrite(c->db,c->argv[1]);
198     if (o == NULL) {
199         /* Return 0 when setting nothing on a non-existing string */
200         if (sdslen(value) == 0) {
201             addReply(c,shared.czero);
202             return;
203         }
204 
205         /* Return when the resulting string exceeds allowed size */
206         if (checkStringLength(c,offset+sdslen(value)) != C_OK)
207             return;
208 
209         o = createObject(OBJ_STRING,sdsnewlen(NULL, offset+sdslen(value)));
210         dbAdd(c->db,c->argv[1],o);
211     } else {
212         size_t olen;
213 
214         /* Key exists, check type */
215         if (checkType(c,o,OBJ_STRING))
216             return;
217 
218         /* Return existing string length when setting nothing */
219         olen = stringObjectLen(o);
220         if (sdslen(value) == 0) {
221             addReplyLongLong(c,olen);
222             return;
223         }
224 
225         /* Return when the resulting string exceeds allowed size */
226         if (checkStringLength(c,offset+sdslen(value)) != C_OK)
227             return;
228 
229         /* Create a copy when the object is shared or encoded. */
230         o = dbUnshareStringValue(c->db,c->argv[1],o);
231     }
232 
233     if (sdslen(value) > 0) {
234         o->ptr = sdsgrowzero(o->ptr,offset+sdslen(value));
235         memcpy((char*)o->ptr+offset,value,sdslen(value));
236         signalModifiedKey(c->db,c->argv[1]);
237         notifyKeyspaceEvent(NOTIFY_STRING,
238             "setrange",c->argv[1],c->db->id);
239         server.dirty++;
240     }
241     addReplyLongLong(c,sdslen(o->ptr));
242 }
243 
getrangeCommand(client * c)244 void getrangeCommand(client *c) {
245     robj *o;
246     long long start, end;
247     char *str, llbuf[32];
248     size_t strlen;
249 
250     if (getLongLongFromObjectOrReply(c,c->argv[2],&start,NULL) != C_OK)
251         return;
252     if (getLongLongFromObjectOrReply(c,c->argv[3],&end,NULL) != C_OK)
253         return;
254     if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.emptybulk)) == NULL ||
255         checkType(c,o,OBJ_STRING)) return;
256 
257     if (o->encoding == OBJ_ENCODING_INT) {
258         str = llbuf;
259         strlen = ll2string(llbuf,sizeof(llbuf),(long)o->ptr);
260     } else {
261         str = o->ptr;
262         strlen = sdslen(str);
263     }
264 
265     /* Convert negative indexes */
266     if (start < 0 && end < 0 && start > end) {
267         addReply(c,shared.emptybulk);
268         return;
269     }
270     if (start < 0) start = strlen+start;
271     if (end < 0) end = strlen+end;
272     if (start < 0) start = 0;
273     if (end < 0) end = 0;
274     if ((unsigned long long)end >= strlen) end = strlen-1;
275 
276     /* Precondition: end >= 0 && end < strlen, so the only condition where
277      * nothing can be returned is: start > end. */
278     if (start > end || strlen == 0) {
279         addReply(c,shared.emptybulk);
280     } else {
281         addReplyBulkCBuffer(c,(char*)str+start,end-start+1);
282     }
283 }
284 
mgetCommand(client * c)285 void mgetCommand(client *c) {
286     int j;
287 
288     addReplyMultiBulkLen(c,c->argc-1);
289     for (j = 1; j < c->argc; j++) {
290         robj *o = lookupKeyRead(c->db,c->argv[j]);
291         if (o == NULL) {
292             addReply(c,shared.nullbulk);
293         } else {
294             if (o->type != OBJ_STRING) {
295                 addReply(c,shared.nullbulk);
296             } else {
297                 addReplyBulk(c,o);
298             }
299         }
300     }
301 }
302 
msetGenericCommand(client * c,int nx)303 void msetGenericCommand(client *c, int nx) {
304     int j;
305 
306     if ((c->argc % 2) == 0) {
307         addReplyError(c,"wrong number of arguments for MSET");
308         return;
309     }
310 
311     /* Handle the NX flag. The MSETNX semantic is to return zero and don't
312      * set anything if at least one key alerady exists. */
313     if (nx) {
314         for (j = 1; j < c->argc; j += 2) {
315             if (lookupKeyWrite(c->db,c->argv[j]) != NULL) {
316                 addReply(c, shared.czero);
317                 return;
318             }
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         notifyKeyspaceEvent(NOTIFY_STRING,"set",c->argv[j],c->db->id);
326     }
327     server.dirty += (c->argc-1)/2;
328     addReply(c, nx ? shared.cone : shared.ok);
329 }
330 
msetCommand(client * c)331 void msetCommand(client *c) {
332     msetGenericCommand(c,0);
333 }
334 
msetnxCommand(client * c)335 void msetnxCommand(client *c) {
336     msetGenericCommand(c,1);
337 }
338 
incrDecrCommand(client * c,long long incr)339 void incrDecrCommand(client *c, long long incr) {
340     long long value, oldvalue;
341     robj *o, *new;
342 
343     o = lookupKeyWrite(c->db,c->argv[1]);
344     if (o != NULL && checkType(c,o,OBJ_STRING)) return;
345     if (getLongLongFromObjectOrReply(c,o,&value,NULL) != C_OK) return;
346 
347     oldvalue = value;
348     if ((incr < 0 && oldvalue < 0 && incr < (LLONG_MIN-oldvalue)) ||
349         (incr > 0 && oldvalue > 0 && incr > (LLONG_MAX-oldvalue))) {
350         addReplyError(c,"increment or decrement would overflow");
351         return;
352     }
353     value += incr;
354 
355     if (o && o->refcount == 1 && o->encoding == OBJ_ENCODING_INT &&
356         (value < 0 || value >= OBJ_SHARED_INTEGERS) &&
357         value >= LONG_MIN && value <= LONG_MAX)
358     {
359         new = o;
360         o->ptr = (void*)((long)value);
361     } else {
362         new = createStringObjectFromLongLongForValue(value);
363         if (o) {
364             dbOverwrite(c->db,c->argv[1],new);
365         } else {
366             dbAdd(c->db,c->argv[1],new);
367         }
368     }
369     signalModifiedKey(c->db,c->argv[1]);
370     notifyKeyspaceEvent(NOTIFY_STRING,"incrby",c->argv[1],c->db->id);
371     server.dirty++;
372     addReply(c,shared.colon);
373     addReply(c,new);
374     addReply(c,shared.crlf);
375 }
376 
incrCommand(client * c)377 void incrCommand(client *c) {
378     incrDecrCommand(c,1);
379 }
380 
decrCommand(client * c)381 void decrCommand(client *c) {
382     incrDecrCommand(c,-1);
383 }
384 
incrbyCommand(client * c)385 void incrbyCommand(client *c) {
386     long long incr;
387 
388     if (getLongLongFromObjectOrReply(c, c->argv[2], &incr, NULL) != C_OK) return;
389     incrDecrCommand(c,incr);
390 }
391 
decrbyCommand(client * c)392 void decrbyCommand(client *c) {
393     long long incr;
394 
395     if (getLongLongFromObjectOrReply(c, c->argv[2], &incr, NULL) != C_OK) return;
396     incrDecrCommand(c,-incr);
397 }
398 
incrbyfloatCommand(client * c)399 void incrbyfloatCommand(client *c) {
400     long double incr, value;
401     robj *o, *new, *aux;
402 
403     o = lookupKeyWrite(c->db,c->argv[1]);
404     if (o != NULL && checkType(c,o,OBJ_STRING)) return;
405     if (getLongDoubleFromObjectOrReply(c,o,&value,NULL) != C_OK ||
406         getLongDoubleFromObjectOrReply(c,c->argv[2],&incr,NULL) != C_OK)
407         return;
408 
409     value += incr;
410     if (isnan(value) || isinf(value)) {
411         addReplyError(c,"increment would produce NaN or Infinity");
412         return;
413     }
414     new = createStringObjectFromLongDouble(value,1);
415     if (o)
416         dbOverwrite(c->db,c->argv[1],new);
417     else
418         dbAdd(c->db,c->argv[1],new);
419     signalModifiedKey(c->db,c->argv[1]);
420     notifyKeyspaceEvent(NOTIFY_STRING,"incrbyfloat",c->argv[1],c->db->id);
421     server.dirty++;
422     addReplyBulk(c,new);
423 
424     /* Always replicate INCRBYFLOAT as a SET command with the final value
425      * in order to make sure that differences in float precision or formatting
426      * will not create differences in replicas or after an AOF restart. */
427     aux = createStringObject("SET",3);
428     rewriteClientCommandArgument(c,0,aux);
429     decrRefCount(aux);
430     rewriteClientCommandArgument(c,2,new);
431 }
432 
appendCommand(client * c)433 void appendCommand(client *c) {
434     size_t totlen;
435     robj *o, *append;
436 
437     o = lookupKeyWrite(c->db,c->argv[1]);
438     if (o == NULL) {
439         /* Create the key */
440         c->argv[2] = tryObjectEncoding(c->argv[2]);
441         dbAdd(c->db,c->argv[1],c->argv[2]);
442         incrRefCount(c->argv[2]);
443         totlen = stringObjectLen(c->argv[2]);
444     } else {
445         /* Key exists, check type */
446         if (checkType(c,o,OBJ_STRING))
447             return;
448 
449         /* "append" is an argument, so always an sds */
450         append = c->argv[2];
451         totlen = stringObjectLen(o)+sdslen(append->ptr);
452         if (checkStringLength(c,totlen) != C_OK)
453             return;
454 
455         /* Append the value */
456         o = dbUnshareStringValue(c->db,c->argv[1],o);
457         o->ptr = sdscatlen(o->ptr,append->ptr,sdslen(append->ptr));
458         totlen = sdslen(o->ptr);
459     }
460     signalModifiedKey(c->db,c->argv[1]);
461     notifyKeyspaceEvent(NOTIFY_STRING,"append",c->argv[1],c->db->id);
462     server.dirty++;
463     addReplyLongLong(c,totlen);
464 }
465 
strlenCommand(client * c)466 void strlenCommand(client *c) {
467     robj *o;
468     if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL ||
469         checkType(c,o,OBJ_STRING)) return;
470     addReplyLongLong(c,stringObjectLen(o));
471 }
472