]> git.saurik.com Git - redis.git/blob - src/t_string.c
BITOP: handle integer encoded objects correctly.
[redis.git] / src / t_string.c
1 #include "redis.h"
2 #include <math.h> /* isnan(), isinf() */
3
4 /*-----------------------------------------------------------------------------
5 * String Commands
6 *----------------------------------------------------------------------------*/
7
8 static 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
16 void 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
39 void 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
44 void 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
49 void 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
54 void 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
59 int 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
74 void getCommand(redisClient *c) {
75 getGenericCommand(c);
76 }
77
78 void 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
85 void 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
93 if (offset < 0) {
94 addReplyError(c,"offset is out of range");
95 return;
96 }
97
98 o = lookupKeyWrite(c->db,c->argv[1]);
99 if (o == NULL) {
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 {
113 size_t olen;
114
115 /* Key exists, check type */
116 if (checkType(c,o,REDIS_STRING))
117 return;
118
119 /* Return existing string length when setting nothing */
120 olen = stringObjectLen(o);
121 if (sdslen(value) == 0) {
122 addReplyLongLong(c,olen);
123 return;
124 }
125
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);
135 dbOverwrite(c->db,c->argv[1],o);
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));
142 signalModifiedKey(c->db,c->argv[1]);
143 server.dirty++;
144 }
145 addReplyLongLong(c,sdslen(o->ptr));
146 }
147
148 void 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;
158 if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.emptybulk)) == NULL ||
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) {
179 addReply(c,shared.emptybulk);
180 } else {
181 addReplyBulkCBuffer(c,(char*)str+start,end-start+1);
182 }
183 }
184
185 void mgetCommand(redisClient *c) {
186 int j;
187
188 addReplyMultiBulkLen(c,c->argc-1);
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
203 void msetGenericCommand(redisClient *c, int nx) {
204 int j, busykeys = 0;
205
206 if ((c->argc % 2) == 0) {
207 addReplyError(c,"wrong number of arguments for MSET");
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 }
218 if (busykeys) {
219 addReply(c, shared.czero);
220 return;
221 }
222 }
223
224 for (j = 1; j < c->argc; j += 2) {
225 c->argv[j+1] = tryObjectEncoding(c->argv[j+1]);
226 setKey(c->db,c->argv[j],c->argv[j+1]);
227 }
228 server.dirty += (c->argc-1)/2;
229 addReply(c, nx ? shared.cone : shared.ok);
230 }
231
232 void msetCommand(redisClient *c) {
233 msetGenericCommand(c,0);
234 }
235
236 void msetnxCommand(redisClient *c) {
237 msetGenericCommand(c,1);
238 }
239
240 void incrDecrCommand(redisClient *c, long long incr) {
241 long long value, oldvalue;
242 robj *o, *new;
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
248 oldvalue = value;
249 if ((incr < 0 && oldvalue < 0 && incr < (LLONG_MIN-oldvalue)) ||
250 (incr > 0 && oldvalue > 0 && incr > (LLONG_MAX-oldvalue))) {
251 addReplyError(c,"increment or decrement would overflow");
252 return;
253 }
254 value += incr;
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);
260 signalModifiedKey(c->db,c->argv[1]);
261 server.dirty++;
262 addReply(c,shared.colon);
263 addReply(c,new);
264 addReply(c,shared.crlf);
265 }
266
267 void incrCommand(redisClient *c) {
268 incrDecrCommand(c,1);
269 }
270
271 void decrCommand(redisClient *c) {
272 incrDecrCommand(c,-1);
273 }
274
275 void 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
282 void 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
289 void incrbyfloatCommand(redisClient *c) {
290 long double incr, value;
291 robj *o, *new, *aux;
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);
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);
320 }
321
322 void appendCommand(redisClient *c) {
323 size_t totlen;
324 robj *o, *append;
325
326 o = lookupKeyWrite(c->db,c->argv[1]);
327 if (o == NULL) {
328 /* Create the key */
329 c->argv[2] = tryObjectEncoding(c->argv[2]);
330 dbAdd(c->db,c->argv[1],c->argv[2]);
331 incrRefCount(c->argv[2]);
332 totlen = stringObjectLen(c->argv[2]);
333 } else {
334 /* Key exists, check type */
335 if (checkType(c,o,REDIS_STRING))
336 return;
337
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)
342 return;
343
344 /* If the object is shared or encoded, we have to make a copy */
345 if (o->refcount != 1 || o->encoding != REDIS_ENCODING_RAW) {
346 robj *decoded = getDecodedObject(o);
347 o = createStringObject(decoded->ptr, sdslen(decoded->ptr));
348 decrRefCount(decoded);
349 dbOverwrite(c->db,c->argv[1],o);
350 }
351
352 /* Append the value */
353 o->ptr = sdscatlen(o->ptr,append->ptr,sdslen(append->ptr));
354 totlen = sdslen(o->ptr);
355 }
356 signalModifiedKey(c->db,c->argv[1]);
357 server.dirty++;
358 addReplyLongLong(c,totlen);
359 }
360
361 void strlenCommand(redisClient *c) {
362 robj *o;
363 if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL ||
364 checkType(c,o,REDIS_STRING)) return;
365 addReplyLongLong(c,stringObjectLen(o));
366 }