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