]> git.saurik.com Git - redis.git/blame - src/t_hash.c
Add regression test and fix for >255 byte string entries
[redis.git] / src / t_hash.c
CommitLineData
e2641e09 1#include "redis.h"
2
3#include <math.h>
4
5/*-----------------------------------------------------------------------------
6 * Hash type API
7 *----------------------------------------------------------------------------*/
8
9/* Check the length of a number of objects to see if we need to convert a
10 * zipmap to a real hash. Note that we only check string encoded objects
11 * as their string length can be queried in constant time. */
12void hashTypeTryConversion(robj *subject, robj **argv, int start, int end) {
13 int i;
14 if (subject->encoding != REDIS_ENCODING_ZIPMAP) return;
15
16 for (i = start; i <= end; i++) {
17 if (argv[i]->encoding == REDIS_ENCODING_RAW &&
18 sdslen(argv[i]->ptr) > server.hash_max_zipmap_value)
19 {
20 convertToRealHash(subject);
21 return;
22 }
23 }
24}
25
26/* Encode given objects in-place when the hash uses a dict. */
27void hashTypeTryObjectEncoding(robj *subject, robj **o1, robj **o2) {
28 if (subject->encoding == REDIS_ENCODING_HT) {
29 if (o1) *o1 = tryObjectEncoding(*o1);
30 if (o2) *o2 = tryObjectEncoding(*o2);
31 }
32}
33
34/* Get the value from a hash identified by key. Returns either a string
35 * object or NULL if the value cannot be found. The refcount of the object
36 * is always increased by 1 when the value was found. */
37robj *hashTypeGet(robj *o, robj *key) {
38 robj *value = NULL;
39 if (o->encoding == REDIS_ENCODING_ZIPMAP) {
40 unsigned char *v;
41 unsigned int vlen;
42 key = getDecodedObject(key);
43 if (zipmapGet(o->ptr,key->ptr,sdslen(key->ptr),&v,&vlen)) {
44 value = createStringObject((char*)v,vlen);
45 }
46 decrRefCount(key);
47 } else {
48 dictEntry *de = dictFind(o->ptr,key);
49 if (de != NULL) {
50 value = dictGetEntryVal(de);
51 incrRefCount(value);
52 }
53 }
54 return value;
55}
56
57/* Test if the key exists in the given hash. Returns 1 if the key
58 * exists and 0 when it doesn't. */
59int hashTypeExists(robj *o, robj *key) {
60 if (o->encoding == REDIS_ENCODING_ZIPMAP) {
61 key = getDecodedObject(key);
62 if (zipmapExists(o->ptr,key->ptr,sdslen(key->ptr))) {
63 decrRefCount(key);
64 return 1;
65 }
66 decrRefCount(key);
67 } else {
68 if (dictFind(o->ptr,key) != NULL) {
69 return 1;
70 }
71 }
72 return 0;
73}
74
75/* Add an element, discard the old if the key already exists.
76 * Return 0 on insert and 1 on update. */
77int hashTypeSet(robj *o, robj *key, robj *value) {
78 int update = 0;
79 if (o->encoding == REDIS_ENCODING_ZIPMAP) {
80 key = getDecodedObject(key);
81 value = getDecodedObject(value);
82 o->ptr = zipmapSet(o->ptr,
83 key->ptr,sdslen(key->ptr),
84 value->ptr,sdslen(value->ptr), &update);
85 decrRefCount(key);
86 decrRefCount(value);
87
88 /* Check if the zipmap needs to be upgraded to a real hash table */
89 if (zipmapLen(o->ptr) > server.hash_max_zipmap_entries)
90 convertToRealHash(o);
91 } else {
92 if (dictReplace(o->ptr,key,value)) {
93 /* Insert */
94 incrRefCount(key);
95 } else {
96 /* Update */
97 update = 1;
98 }
99 incrRefCount(value);
100 }
101 return update;
102}
103
104/* Delete an element from a hash.
105 * Return 1 on deleted and 0 on not found. */
106int hashTypeDelete(robj *o, robj *key) {
107 int deleted = 0;
108 if (o->encoding == REDIS_ENCODING_ZIPMAP) {
109 key = getDecodedObject(key);
110 o->ptr = zipmapDel(o->ptr,key->ptr,sdslen(key->ptr), &deleted);
111 decrRefCount(key);
112 } else {
113 deleted = dictDelete((dict*)o->ptr,key) == DICT_OK;
114 /* Always check if the dictionary needs a resize after a delete. */
115 if (deleted && htNeedsResize(o->ptr)) dictResize(o->ptr);
116 }
117 return deleted;
118}
119
120/* Return the number of elements in a hash. */
121unsigned long hashTypeLength(robj *o) {
122 return (o->encoding == REDIS_ENCODING_ZIPMAP) ?
123 zipmapLen((unsigned char*)o->ptr) : dictSize((dict*)o->ptr);
124}
125
126hashTypeIterator *hashTypeInitIterator(robj *subject) {
127 hashTypeIterator *hi = zmalloc(sizeof(hashTypeIterator));
128 hi->encoding = subject->encoding;
129 if (hi->encoding == REDIS_ENCODING_ZIPMAP) {
130 hi->zi = zipmapRewind(subject->ptr);
131 } else if (hi->encoding == REDIS_ENCODING_HT) {
132 hi->di = dictGetIterator(subject->ptr);
133 } else {
134 redisAssert(NULL);
135 }
136 return hi;
137}
138
139void hashTypeReleaseIterator(hashTypeIterator *hi) {
140 if (hi->encoding == REDIS_ENCODING_HT) {
141 dictReleaseIterator(hi->di);
142 }
143 zfree(hi);
144}
145
146/* Move to the next entry in the hash. Return REDIS_OK when the next entry
147 * could be found and REDIS_ERR when the iterator reaches the end. */
148int hashTypeNext(hashTypeIterator *hi) {
149 if (hi->encoding == REDIS_ENCODING_ZIPMAP) {
150 if ((hi->zi = zipmapNext(hi->zi, &hi->zk, &hi->zklen,
151 &hi->zv, &hi->zvlen)) == NULL) return REDIS_ERR;
152 } else {
153 if ((hi->de = dictNext(hi->di)) == NULL) return REDIS_ERR;
154 }
155 return REDIS_OK;
156}
157
158/* Get key or value object at current iteration position.
159 * This increases the refcount of the field object by 1. */
160robj *hashTypeCurrent(hashTypeIterator *hi, int what) {
161 robj *o;
162 if (hi->encoding == REDIS_ENCODING_ZIPMAP) {
163 if (what & REDIS_HASH_KEY) {
164 o = createStringObject((char*)hi->zk,hi->zklen);
165 } else {
166 o = createStringObject((char*)hi->zv,hi->zvlen);
167 }
168 } else {
169 if (what & REDIS_HASH_KEY) {
170 o = dictGetEntryKey(hi->de);
171 } else {
172 o = dictGetEntryVal(hi->de);
173 }
174 incrRefCount(o);
175 }
176 return o;
177}
178
179robj *hashTypeLookupWriteOrCreate(redisClient *c, robj *key) {
180 robj *o = lookupKeyWrite(c->db,key);
181 if (o == NULL) {
182 o = createHashObject();
183 dbAdd(c->db,key,o);
184 } else {
185 if (o->type != REDIS_HASH) {
186 addReply(c,shared.wrongtypeerr);
187 return NULL;
188 }
189 }
190 return o;
191}
192
193void convertToRealHash(robj *o) {
194 unsigned char *key, *val, *p, *zm = o->ptr;
195 unsigned int klen, vlen;
196 dict *dict = dictCreate(&hashDictType,NULL);
197
198 redisAssert(o->type == REDIS_HASH && o->encoding != REDIS_ENCODING_HT);
199 p = zipmapRewind(zm);
200 while((p = zipmapNext(p,&key,&klen,&val,&vlen)) != NULL) {
201 robj *keyobj, *valobj;
202
203 keyobj = createStringObject((char*)key,klen);
204 valobj = createStringObject((char*)val,vlen);
205 keyobj = tryObjectEncoding(keyobj);
206 valobj = tryObjectEncoding(valobj);
207 dictAdd(dict,keyobj,valobj);
208 }
209 o->encoding = REDIS_ENCODING_HT;
210 o->ptr = dict;
211 zfree(zm);
212}
213
214/*-----------------------------------------------------------------------------
215 * Hash type commands
216 *----------------------------------------------------------------------------*/
217
218void hsetCommand(redisClient *c) {
219 int update;
220 robj *o;
221
222 if ((o = hashTypeLookupWriteOrCreate(c,c->argv[1])) == NULL) return;
223 hashTypeTryConversion(o,c->argv,2,3);
224 hashTypeTryObjectEncoding(o,&c->argv[2], &c->argv[3]);
225 update = hashTypeSet(o,c->argv[2],c->argv[3]);
226 addReply(c, update ? shared.czero : shared.cone);
5b4bff9c 227 touchWatchedKey(c->db,c->argv[1]);
e2641e09 228 server.dirty++;
229}
230
231void hsetnxCommand(redisClient *c) {
232 robj *o;
233 if ((o = hashTypeLookupWriteOrCreate(c,c->argv[1])) == NULL) return;
234 hashTypeTryConversion(o,c->argv,2,3);
235
236 if (hashTypeExists(o, c->argv[2])) {
237 addReply(c, shared.czero);
238 } else {
239 hashTypeTryObjectEncoding(o,&c->argv[2], &c->argv[3]);
240 hashTypeSet(o,c->argv[2],c->argv[3]);
241 addReply(c, shared.cone);
5b4bff9c 242 touchWatchedKey(c->db,c->argv[1]);
e2641e09 243 server.dirty++;
244 }
245}
246
247void hmsetCommand(redisClient *c) {
248 int i;
249 robj *o;
250
251 if ((c->argc % 2) == 1) {
3ab20376 252 addReplyError(c,"wrong number of arguments for HMSET");
e2641e09 253 return;
254 }
255
256 if ((o = hashTypeLookupWriteOrCreate(c,c->argv[1])) == NULL) return;
257 hashTypeTryConversion(o,c->argv,2,c->argc-1);
258 for (i = 2; i < c->argc; i += 2) {
259 hashTypeTryObjectEncoding(o,&c->argv[i], &c->argv[i+1]);
260 hashTypeSet(o,c->argv[i],c->argv[i+1]);
261 }
262 addReply(c, shared.ok);
5b4bff9c 263 touchWatchedKey(c->db,c->argv[1]);
e2641e09 264 server.dirty++;
265}
266
267void hincrbyCommand(redisClient *c) {
268 long long value, incr;
269 robj *o, *current, *new;
270
271 if (getLongLongFromObjectOrReply(c,c->argv[3],&incr,NULL) != REDIS_OK) return;
272 if ((o = hashTypeLookupWriteOrCreate(c,c->argv[1])) == NULL) return;
273 if ((current = hashTypeGet(o,c->argv[2])) != NULL) {
274 if (getLongLongFromObjectOrReply(c,current,&value,
275 "hash value is not an integer") != REDIS_OK) {
276 decrRefCount(current);
277 return;
278 }
279 decrRefCount(current);
280 } else {
281 value = 0;
282 }
283
284 value += incr;
285 new = createStringObjectFromLongLong(value);
286 hashTypeTryObjectEncoding(o,&c->argv[2],NULL);
287 hashTypeSet(o,c->argv[2],new);
288 decrRefCount(new);
289 addReplyLongLong(c,value);
5b4bff9c 290 touchWatchedKey(c->db,c->argv[1]);
e2641e09 291 server.dirty++;
292}
293
294void hgetCommand(redisClient *c) {
295 robj *o, *value;
296 if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.nullbulk)) == NULL ||
297 checkType(c,o,REDIS_HASH)) return;
298
299 if ((value = hashTypeGet(o,c->argv[2])) != NULL) {
300 addReplyBulk(c,value);
301 decrRefCount(value);
302 } else {
303 addReply(c,shared.nullbulk);
304 }
305}
306
307void hmgetCommand(redisClient *c) {
308 int i;
309 robj *o, *value;
310 o = lookupKeyRead(c->db,c->argv[1]);
311 if (o != NULL && o->type != REDIS_HASH) {
312 addReply(c,shared.wrongtypeerr);
313 }
314
315 /* Note the check for o != NULL happens inside the loop. This is
316 * done because objects that cannot be found are considered to be
317 * an empty hash. The reply should then be a series of NULLs. */
0537e7bf 318 addReplyMultiBulkLen(c,c->argc-2);
e2641e09 319 for (i = 2; i < c->argc; i++) {
320 if (o != NULL && (value = hashTypeGet(o,c->argv[i])) != NULL) {
321 addReplyBulk(c,value);
322 decrRefCount(value);
323 } else {
324 addReply(c,shared.nullbulk);
325 }
326 }
327}
328
329void hdelCommand(redisClient *c) {
330 robj *o;
331 if ((o = lookupKeyWriteOrReply(c,c->argv[1],shared.czero)) == NULL ||
332 checkType(c,o,REDIS_HASH)) return;
333
334 if (hashTypeDelete(o,c->argv[2])) {
335 if (hashTypeLength(o) == 0) dbDelete(c->db,c->argv[1]);
336 addReply(c,shared.cone);
5b4bff9c 337 touchWatchedKey(c->db,c->argv[1]);
e2641e09 338 server.dirty++;
339 } else {
340 addReply(c,shared.czero);
341 }
342}
343
344void hlenCommand(redisClient *c) {
345 robj *o;
346 if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL ||
347 checkType(c,o,REDIS_HASH)) return;
348
b70d3555 349 addReplyLongLong(c,hashTypeLength(o));
e2641e09 350}
351
352void genericHgetallCommand(redisClient *c, int flags) {
b301c1fc 353 robj *o, *obj;
e2641e09 354 unsigned long count = 0;
355 hashTypeIterator *hi;
b301c1fc 356 void *replylen = NULL;
e2641e09 357
358 if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.emptymultibulk)) == NULL
359 || checkType(c,o,REDIS_HASH)) return;
360
b301c1fc 361 replylen = addDeferredMultiBulkLength(c);
e2641e09 362 hi = hashTypeInitIterator(o);
363 while (hashTypeNext(hi) != REDIS_ERR) {
364 if (flags & REDIS_HASH_KEY) {
365 obj = hashTypeCurrent(hi,REDIS_HASH_KEY);
366 addReplyBulk(c,obj);
367 decrRefCount(obj);
368 count++;
369 }
370 if (flags & REDIS_HASH_VALUE) {
371 obj = hashTypeCurrent(hi,REDIS_HASH_VALUE);
372 addReplyBulk(c,obj);
373 decrRefCount(obj);
374 count++;
375 }
376 }
377 hashTypeReleaseIterator(hi);
b301c1fc 378 setDeferredMultiBulkLength(c,replylen,count);
e2641e09 379}
380
381void hkeysCommand(redisClient *c) {
382 genericHgetallCommand(c,REDIS_HASH_KEY);
383}
384
385void hvalsCommand(redisClient *c) {
386 genericHgetallCommand(c,REDIS_HASH_VALUE);
387}
388
389void hgetallCommand(redisClient *c) {
390 genericHgetallCommand(c,REDIS_HASH_KEY|REDIS_HASH_VALUE);
391}
392
393void hexistsCommand(redisClient *c) {
394 robj *o;
395 if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL ||
396 checkType(c,o,REDIS_HASH)) return;
397
398 addReply(c, hashTypeExists(o,c->argv[2]) ? shared.cone : shared.czero);
399}