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