]> git.saurik.com Git - redis.git/blame - src/t_string.c
BITOP command 10x speed improvement.
[redis.git] / src / t_string.c
CommitLineData
e2641e09 1#include "redis.h"
5574b53e 2#include <math.h> /* isnan(), isinf() */
e2641e09 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
12d293ca 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 */
e2641e09 18
19 if (expire) {
12d293ca 20 if (getLongLongFromObjectOrReply(c, expire, &milliseconds, NULL) != REDIS_OK)
e2641e09 21 return;
12d293ca 22 if (milliseconds <= 0) {
3ab20376 23 addReplyError(c,"invalid expire time in SETEX");
e2641e09 24 return;
25 }
12d293ca 26 if (unit == UNIT_SECONDS) milliseconds *= 1000;
e2641e09 27 }
28
f85cd526 29 if (lookupKeyWrite(c->db,key) != NULL && nx) {
30 addReply(c,shared.czero);
31 return;
e2641e09 32 }
f85cd526 33 setKey(c->db,key,val);
e2641e09 34 server.dirty++;
12d293ca 35 if (expire) setExpire(c->db,key,mstime()+milliseconds);
e2641e09 36 addReply(c, nx ? shared.cone : shared.ok);
37}
38
39void setCommand(redisClient *c) {
75b41de8 40 c->argv[2] = tryObjectEncoding(c->argv[2]);
12d293ca 41 setGenericCommand(c,0,c->argv[1],c->argv[2],NULL,0);
e2641e09 42}
43
44void setnxCommand(redisClient *c) {
75b41de8 45 c->argv[2] = tryObjectEncoding(c->argv[2]);
12d293ca 46 setGenericCommand(c,1,c->argv[1],c->argv[2],NULL,0);
e2641e09 47}
48
49void setexCommand(redisClient *c) {
75b41de8 50 c->argv[3] = tryObjectEncoding(c->argv[3]);
12d293ca 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);
e2641e09 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;
75b41de8 80 c->argv[2] = tryObjectEncoding(c->argv[2]);
f85cd526 81 setKey(c->db,c->argv[1],c->argv[2]);
e2641e09 82 server.dirty++;
3c1bf495
PN
83}
84
9f9e1cea
PN
85void setrangeCommand(redisClient *c) {
86 robj *o;
87 long offset;
88 sds value = c->argv[3]->ptr;
89
90 if (getLongFromObjectOrReply(c,c->argv[2],&offset,NULL) != REDIS_OK)
91 return;
92
8f8eeffe
PN
93 if (offset < 0) {
94 addReplyError(c,"offset is out of range");
95 return;
96 }
97
9f9e1cea
PN
98 o = lookupKeyWrite(c->db,c->argv[1]);
99 if (o == NULL) {
9f9e1cea
PN
100 /* Return 0 when setting nothing on a non-existing string */
101 if (sdslen(value) == 0) {
102 addReply(c,shared.czero);
103 return;
104 }
105
106 /* Return when the resulting string exceeds allowed size */
107 if (checkStringLength(c,offset+sdslen(value)) != REDIS_OK)
108 return;
109
110 o = createObject(REDIS_STRING,sdsempty());
111 dbAdd(c->db,c->argv[1],o);
112 } else {
ad1b4f4f 113 size_t olen;
9f9e1cea
PN
114
115 /* Key exists, check type */
116 if (checkType(c,o,REDIS_STRING))
117 return;
118
9f9e1cea 119 /* Return existing string length when setting nothing */
ad1b4f4f 120 olen = stringObjectLen(o);
9f9e1cea
PN
121 if (sdslen(value) == 0) {
122 addReplyLongLong(c,olen);
123 return;
124 }
125
9f9e1cea
PN
126 /* Return when the resulting string exceeds allowed size */
127 if (checkStringLength(c,offset+sdslen(value)) != REDIS_OK)
128 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);
f85cd526 135 dbOverwrite(c->db,c->argv[1],o);
9f9e1cea
PN
136 }
137 }
138
139 if (sdslen(value) > 0) {
140 o->ptr = sdsgrowzero(o->ptr,offset+sdslen(value));
141 memcpy((char*)o->ptr+offset,value,sdslen(value));
cea8c5cd 142 signalModifiedKey(c->db,c->argv[1]);
9f9e1cea
PN
143 server.dirty++;
144 }
145 addReplyLongLong(c,sdslen(o->ptr));
146}
147
ef11bccc
PN
148void getrangeCommand(redisClient *c) {
149 robj *o;
150 long start, end;
151 char *str, llbuf[32];
152 size_t strlen;
153
154 if (getLongFromObjectOrReply(c,c->argv[2],&start,NULL) != REDIS_OK)
155 return;
156 if (getLongFromObjectOrReply(c,c->argv[3],&end,NULL) != REDIS_OK)
157 return;
0b537972 158 if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.emptybulk)) == NULL ||
ef11bccc
PN
159 checkType(c,o,REDIS_STRING)) return;
160
161 if (o->encoding == REDIS_ENCODING_INT) {
162 str = llbuf;
163 strlen = ll2string(llbuf,sizeof(llbuf),(long)o->ptr);
164 } else {
165 str = o->ptr;
166 strlen = sdslen(str);
167 }
168
169 /* Convert negative indexes */
170 if (start < 0) start = strlen+start;
171 if (end < 0) end = strlen+end;
172 if (start < 0) start = 0;
173 if (end < 0) end = 0;
174 if ((unsigned)end >= strlen) end = strlen-1;
175
176 /* Precondition: end >= 0 && end < strlen, so the only condition where
177 * nothing can be returned is: start > end. */
178 if (start > end) {
0b537972 179 addReply(c,shared.emptybulk);
ef11bccc
PN
180 } else {
181 addReplyBulkCBuffer(c,(char*)str+start,end-start+1);
182 }
183}
184
e2641e09 185void mgetCommand(redisClient *c) {
186 int j;
187
0537e7bf 188 addReplyMultiBulkLen(c,c->argc-1);
e2641e09 189 for (j = 1; j < c->argc; j++) {
190 robj *o = lookupKeyRead(c->db,c->argv[j]);
191 if (o == NULL) {
192 addReply(c,shared.nullbulk);
193 } else {
194 if (o->type != REDIS_STRING) {
195 addReply(c,shared.nullbulk);
196 } else {
197 addReplyBulk(c,o);
198 }
199 }
200 }
201}
202
203void msetGenericCommand(redisClient *c, int nx) {
204 int j, busykeys = 0;
205
206 if ((c->argc % 2) == 0) {
3ab20376 207 addReplyError(c,"wrong number of arguments for MSET");
e2641e09 208 return;
209 }
210 /* Handle the NX flag. The MSETNX semantic is to return zero and don't
211 * set nothing at all if at least one already key exists. */
212 if (nx) {
213 for (j = 1; j < c->argc; j += 2) {
214 if (lookupKeyWrite(c->db,c->argv[j]) != NULL) {
215 busykeys++;
216 }
217 }
f85cd526 218 if (busykeys) {
219 addReply(c, shared.czero);
220 return;
221 }
e2641e09 222 }
223
224 for (j = 1; j < c->argc; j += 2) {
225 c->argv[j+1] = tryObjectEncoding(c->argv[j+1]);
f85cd526 226 setKey(c->db,c->argv[j],c->argv[j+1]);
e2641e09 227 }
228 server.dirty += (c->argc-1)/2;
229 addReply(c, nx ? shared.cone : shared.ok);
230}
231
232void msetCommand(redisClient *c) {
233 msetGenericCommand(c,0);
234}
235
236void msetnxCommand(redisClient *c) {
237 msetGenericCommand(c,1);
238}
239
240void incrDecrCommand(redisClient *c, long long incr) {
9d7165e8 241 long long value, oldvalue;
f85cd526 242 robj *o, *new;
e2641e09 243
244 o = lookupKeyWrite(c->db,c->argv[1]);
245 if (o != NULL && checkType(c,o,REDIS_STRING)) return;
246 if (getLongLongFromObjectOrReply(c,o,&value,NULL) != REDIS_OK) return;
247
9d7165e8 248 oldvalue = value;
7c96b467 249 if ((incr < 0 && oldvalue < 0 && incr < (LLONG_MIN-oldvalue)) ||
250 (incr > 0 && oldvalue > 0 && incr > (LLONG_MAX-oldvalue))) {
9d7165e8 251 addReplyError(c,"increment or decrement would overflow");
252 return;
253 }
7c96b467 254 value += incr;
f85cd526 255 new = createStringObjectFromLongLong(value);
256 if (o)
257 dbOverwrite(c->db,c->argv[1],new);
258 else
259 dbAdd(c->db,c->argv[1],new);
cea8c5cd 260 signalModifiedKey(c->db,c->argv[1]);
e2641e09 261 server.dirty++;
262 addReply(c,shared.colon);
f85cd526 263 addReply(c,new);
e2641e09 264 addReply(c,shared.crlf);
265}
266
267void incrCommand(redisClient *c) {
268 incrDecrCommand(c,1);
269}
270
271void decrCommand(redisClient *c) {
272 incrDecrCommand(c,-1);
273}
274
275void incrbyCommand(redisClient *c) {
276 long long incr;
277
278 if (getLongLongFromObjectOrReply(c, c->argv[2], &incr, NULL) != REDIS_OK) return;
279 incrDecrCommand(c,incr);
280}
281
282void decrbyCommand(redisClient *c) {
283 long long incr;
284
285 if (getLongLongFromObjectOrReply(c, c->argv[2], &incr, NULL) != REDIS_OK) return;
286 incrDecrCommand(c,-incr);
287}
288
5574b53e 289void incrbyfloatCommand(redisClient *c) {
290 long double incr, value;
5244d6e5 291 robj *o, *new, *aux;
5574b53e 292
293 o = lookupKeyWrite(c->db,c->argv[1]);
294 if (o != NULL && checkType(c,o,REDIS_STRING)) return;
295 if (getLongDoubleFromObjectOrReply(c,o,&value,NULL) != REDIS_OK ||
296 getLongDoubleFromObjectOrReply(c,c->argv[2],&incr,NULL) != REDIS_OK)
297 return;
298
299 value += incr;
300 if (isnan(value) || isinf(value)) {
301 addReplyError(c,"increment would produce NaN or Infinity");
302 return;
303 }
304 new = createStringObjectFromLongDouble(value);
305 if (o)
306 dbOverwrite(c->db,c->argv[1],new);
307 else
308 dbAdd(c->db,c->argv[1],new);
309 signalModifiedKey(c->db,c->argv[1]);
310 server.dirty++;
311 addReplyBulk(c,new);
5244d6e5 312
313 /* Always replicate INCRBYFLOAT as a SET command with the final value
314 * in order to make sure that differences in float pricision or formatting
315 * will not create differences in replicas or after an AOF restart. */
316 aux = createStringObject("SET",3);
317 rewriteClientCommandArgument(c,0,aux);
318 decrRefCount(aux);
319 rewriteClientCommandArgument(c,2,new);
5574b53e 320}
321
e2641e09 322void appendCommand(redisClient *c) {
e2641e09 323 size_t totlen;
076f88d6 324 robj *o, *append;
e2641e09 325
326 o = lookupKeyWrite(c->db,c->argv[1]);
327 if (o == NULL) {
328 /* Create the key */
1333f98d
PN
329 c->argv[2] = tryObjectEncoding(c->argv[2]);
330 dbAdd(c->db,c->argv[1],c->argv[2]);
e2641e09 331 incrRefCount(c->argv[2]);
332 totlen = stringObjectLen(c->argv[2]);
333 } else {
1333f98d
PN
334 /* Key exists, check type */
335 if (checkType(c,o,REDIS_STRING))
e2641e09 336 return;
076f88d6 337
1333f98d
PN
338 /* "append" is an argument, so always an sds */
339 append = c->argv[2];
340 totlen = stringObjectLen(o)+sdslen(append->ptr);
341 if (checkStringLength(c,totlen) != REDIS_OK)
076f88d6 342 return;
076f88d6
PN
343
344 /* If the object is shared or encoded, we have to make a copy */
e2641e09 345 if (o->refcount != 1 || o->encoding != REDIS_ENCODING_RAW) {
346 robj *decoded = getDecodedObject(o);
e2641e09 347 o = createStringObject(decoded->ptr, sdslen(decoded->ptr));
348 decrRefCount(decoded);
f85cd526 349 dbOverwrite(c->db,c->argv[1],o);
e2641e09 350 }
076f88d6
PN
351
352 /* Append the value */
353 o->ptr = sdscatlen(o->ptr,append->ptr,sdslen(append->ptr));
e2641e09 354 totlen = sdslen(o->ptr);
355 }
cea8c5cd 356 signalModifiedKey(c->db,c->argv[1]);
e2641e09 357 server.dirty++;
b70d3555 358 addReplyLongLong(c,totlen);
e2641e09 359}
360
80091bba 361void strlenCommand(redisClient *c) {
362 robj *o;
80091bba 363 if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL ||
364 checkType(c,o,REDIS_STRING)) return;
ad1b4f4f 365 addReplyLongLong(c,stringObjectLen(o));
80091bba 366}