]> git.saurik.com Git - redis.git/blame - src/t_string.c
Safer handling of MULTI/EXEC on errors.
[redis.git] / src / t_string.c
CommitLineData
d288ee65 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
e2641e09 30#include "redis.h"
5574b53e 31#include <math.h> /* isnan(), isinf() */
e2641e09 32
33/*-----------------------------------------------------------------------------
34 * String Commands
35 *----------------------------------------------------------------------------*/
36
9f9e1cea
PN
37static 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
12d293ca 45void setGenericCommand(redisClient *c, int nx, robj *key, robj *val, robj *expire, int unit) {
46 long long milliseconds = 0; /* initialized to avoid an harmness warning */
e2641e09 47
48 if (expire) {
12d293ca 49 if (getLongLongFromObjectOrReply(c, expire, &milliseconds, NULL) != REDIS_OK)
e2641e09 50 return;
12d293ca 51 if (milliseconds <= 0) {
3ab20376 52 addReplyError(c,"invalid expire time in SETEX");
e2641e09 53 return;
54 }
12d293ca 55 if (unit == UNIT_SECONDS) milliseconds *= 1000;
e2641e09 56 }
57
4d9bd535 58 if (nx && lookupKeyWrite(c->db,key) != NULL) {
f85cd526 59 addReply(c,shared.czero);
60 return;
e2641e09 61 }
f85cd526 62 setKey(c->db,key,val);
e2641e09 63 server.dirty++;
12d293ca 64 if (expire) setExpire(c->db,key,mstime()+milliseconds);
e2641e09 65 addReply(c, nx ? shared.cone : shared.ok);
66}
67
68void setCommand(redisClient *c) {
75b41de8 69 c->argv[2] = tryObjectEncoding(c->argv[2]);
12d293ca 70 setGenericCommand(c,0,c->argv[1],c->argv[2],NULL,0);
e2641e09 71}
72
73void setnxCommand(redisClient *c) {
75b41de8 74 c->argv[2] = tryObjectEncoding(c->argv[2]);
12d293ca 75 setGenericCommand(c,1,c->argv[1],c->argv[2],NULL,0);
e2641e09 76}
77
78void setexCommand(redisClient *c) {
75b41de8 79 c->argv[3] = tryObjectEncoding(c->argv[3]);
12d293ca 80 setGenericCommand(c,0,c->argv[1],c->argv[3],c->argv[2],UNIT_SECONDS);
81}
82
83void 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);
e2641e09 86}
87
88int 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
103void getCommand(redisClient *c) {
104 getGenericCommand(c);
105}
106
107void getsetCommand(redisClient *c) {
108 if (getGenericCommand(c) == REDIS_ERR) return;
75b41de8 109 c->argv[2] = tryObjectEncoding(c->argv[2]);
f85cd526 110 setKey(c->db,c->argv[1],c->argv[2]);
e2641e09 111 server.dirty++;
3c1bf495
PN
112}
113
9f9e1cea
PN
114void 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
8f8eeffe
PN
122 if (offset < 0) {
123 addReplyError(c,"offset is out of range");
124 return;
125 }
126
9f9e1cea
PN
127 o = lookupKeyWrite(c->db,c->argv[1]);
128 if (o == NULL) {
9f9e1cea
PN
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 {
ad1b4f4f 142 size_t olen;
9f9e1cea
PN
143
144 /* Key exists, check type */
145 if (checkType(c,o,REDIS_STRING))
146 return;
147
9f9e1cea 148 /* Return existing string length when setting nothing */
ad1b4f4f 149 olen = stringObjectLen(o);
9f9e1cea
PN
150 if (sdslen(value) == 0) {
151 addReplyLongLong(c,olen);
152 return;
153 }
154
9f9e1cea
PN
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);
f85cd526 164 dbOverwrite(c->db,c->argv[1],o);
9f9e1cea
PN
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));
cea8c5cd 171 signalModifiedKey(c->db,c->argv[1]);
9f9e1cea
PN
172 server.dirty++;
173 }
174 addReplyLongLong(c,sdslen(o->ptr));
175}
176
ef11bccc
PN
177void 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;
0b537972 187 if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.emptybulk)) == NULL ||
ef11bccc
PN
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) {
0b537972 208 addReply(c,shared.emptybulk);
ef11bccc
PN
209 } else {
210 addReplyBulkCBuffer(c,(char*)str+start,end-start+1);
211 }
212}
213
e2641e09 214void mgetCommand(redisClient *c) {
215 int j;
216
0537e7bf 217 addReplyMultiBulkLen(c,c->argc-1);
e2641e09 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
232void msetGenericCommand(redisClient *c, int nx) {
233 int j, busykeys = 0;
234
235 if ((c->argc % 2) == 0) {
3ab20376 236 addReplyError(c,"wrong number of arguments for MSET");
e2641e09 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 }
f85cd526 247 if (busykeys) {
248 addReply(c, shared.czero);
249 return;
250 }
e2641e09 251 }
252
253 for (j = 1; j < c->argc; j += 2) {
254 c->argv[j+1] = tryObjectEncoding(c->argv[j+1]);
f85cd526 255 setKey(c->db,c->argv[j],c->argv[j+1]);
e2641e09 256 }
257 server.dirty += (c->argc-1)/2;
258 addReply(c, nx ? shared.cone : shared.ok);
259}
260
261void msetCommand(redisClient *c) {
262 msetGenericCommand(c,0);
263}
264
265void msetnxCommand(redisClient *c) {
266 msetGenericCommand(c,1);
267}
268
269void incrDecrCommand(redisClient *c, long long incr) {
9d7165e8 270 long long value, oldvalue;
f85cd526 271 robj *o, *new;
e2641e09 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
9d7165e8 277 oldvalue = value;
7c96b467 278 if ((incr < 0 && oldvalue < 0 && incr < (LLONG_MIN-oldvalue)) ||
279 (incr > 0 && oldvalue > 0 && incr > (LLONG_MAX-oldvalue))) {
9d7165e8 280 addReplyError(c,"increment or decrement would overflow");
281 return;
282 }
7c96b467 283 value += incr;
f85cd526 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);
cea8c5cd 289 signalModifiedKey(c->db,c->argv[1]);
e2641e09 290 server.dirty++;
291 addReply(c,shared.colon);
f85cd526 292 addReply(c,new);
e2641e09 293 addReply(c,shared.crlf);
294}
295
296void incrCommand(redisClient *c) {
297 incrDecrCommand(c,1);
298}
299
300void decrCommand(redisClient *c) {
301 incrDecrCommand(c,-1);
302}
303
304void 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
311void 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
5574b53e 318void incrbyfloatCommand(redisClient *c) {
319 long double incr, value;
5244d6e5 320 robj *o, *new, *aux;
5574b53e 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);
5244d6e5 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);
5574b53e 349}
350
e2641e09 351void appendCommand(redisClient *c) {
e2641e09 352 size_t totlen;
076f88d6 353 robj *o, *append;
e2641e09 354
355 o = lookupKeyWrite(c->db,c->argv[1]);
356 if (o == NULL) {
357 /* Create the key */
1333f98d
PN
358 c->argv[2] = tryObjectEncoding(c->argv[2]);
359 dbAdd(c->db,c->argv[1],c->argv[2]);
e2641e09 360 incrRefCount(c->argv[2]);
361 totlen = stringObjectLen(c->argv[2]);
362 } else {
1333f98d
PN
363 /* Key exists, check type */
364 if (checkType(c,o,REDIS_STRING))
e2641e09 365 return;
076f88d6 366
1333f98d
PN
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)
076f88d6 371 return;
076f88d6
PN
372
373 /* If the object is shared or encoded, we have to make a copy */
e2641e09 374 if (o->refcount != 1 || o->encoding != REDIS_ENCODING_RAW) {
375 robj *decoded = getDecodedObject(o);
e2641e09 376 o = createStringObject(decoded->ptr, sdslen(decoded->ptr));
377 decrRefCount(decoded);
f85cd526 378 dbOverwrite(c->db,c->argv[1],o);
e2641e09 379 }
076f88d6
PN
380
381 /* Append the value */
382 o->ptr = sdscatlen(o->ptr,append->ptr,sdslen(append->ptr));
e2641e09 383 totlen = sdslen(o->ptr);
384 }
cea8c5cd 385 signalModifiedKey(c->db,c->argv[1]);
e2641e09 386 server.dirty++;
b70d3555 387 addReplyLongLong(c,totlen);
e2641e09 388}
389
80091bba 390void strlenCommand(redisClient *c) {
391 robj *o;
80091bba 392 if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL ||
393 checkType(c,o,REDIS_STRING)) return;
ad1b4f4f 394 addReplyLongLong(c,stringObjectLen(o));
80091bba 395}