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