]> git.saurik.com Git - redis.git/blob - src/t_string.c
Test: fixed osx "leaks" support in test.
[redis.git] / src / t_string.c
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 an 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 pricision 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 }