]> git.saurik.com Git - redis.git/blame_incremental - src/t_hash.c
Query the archive to provide a complete KEYS list.
[redis.git] / src / t_hash.c
... / ...
CommitLineData
1/*
2 * Copyright (c) 2009-2012, Salvatore Sanfilippo <antirez at gmail dot com>
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 *
8 * * Redistributions of source code must retain the above copyright notice,
9 * this list of conditions and the following disclaimer.
10 * * Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 * * Neither the name of Redis nor the names of its contributors may be used
14 * to endorse or promote products derived from this software without
15 * specific prior written permission.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
18 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
21 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
22 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
25 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27 * POSSIBILITY OF SUCH DAMAGE.
28 */
29
30#include "redis.h"
31#include <math.h>
32
33/*-----------------------------------------------------------------------------
34 * Hash type API
35 *----------------------------------------------------------------------------*/
36
37/* Check the length of a number of objects to see if we need to convert a
38 * ziplist to a real hash. Note that we only check string encoded objects
39 * as their string length can be queried in constant time. */
40void hashTypeTryConversion(robj *o, robj **argv, int start, int end) {
41 int i;
42
43 if (o->encoding != REDIS_ENCODING_ZIPLIST) return;
44
45 for (i = start; i <= end; i++) {
46 if (argv[i]->encoding == REDIS_ENCODING_RAW &&
47 sdslen(argv[i]->ptr) > server.hash_max_ziplist_value)
48 {
49 hashTypeConvert(o, REDIS_ENCODING_HT);
50 break;
51 }
52 }
53}
54
55/* Encode given objects in-place when the hash uses a dict. */
56void hashTypeTryObjectEncoding(robj *subject, robj **o1, robj **o2) {
57 if (subject->encoding == REDIS_ENCODING_HT) {
58 if (o1) *o1 = tryObjectEncoding(*o1);
59 if (o2) *o2 = tryObjectEncoding(*o2);
60 }
61}
62
63/* Get the value from a ziplist encoded hash, identified by field.
64 * Returns -1 when the field cannot be found. */
65int hashTypeGetFromZiplist(robj *o, robj *field,
66 unsigned char **vstr,
67 unsigned int *vlen,
68 long long *vll)
69{
70 unsigned char *zl, *fptr = NULL, *vptr = NULL;
71 int ret;
72
73 redisAssert(o->encoding == REDIS_ENCODING_ZIPLIST);
74
75 field = getDecodedObject(field);
76
77 zl = o->ptr;
78 fptr = ziplistIndex(zl, ZIPLIST_HEAD);
79 if (fptr != NULL) {
80 fptr = ziplistFind(fptr, field->ptr, sdslen(field->ptr), 1);
81 if (fptr != NULL) {
82 /* Grab pointer to the value (fptr points to the field) */
83 vptr = ziplistNext(zl, fptr);
84 redisAssert(vptr != NULL);
85 }
86 }
87
88 decrRefCount(field);
89
90 if (vptr != NULL) {
91 ret = ziplistGet(vptr, vstr, vlen, vll);
92 redisAssert(ret);
93 return 0;
94 }
95
96 return -1;
97}
98
99/* Get the value from a hash table encoded hash, identified by field.
100 * Returns -1 when the field cannot be found. */
101int hashTypeGetFromHashTable(robj *o, robj *field, robj **value) {
102 dictEntry *de;
103
104 redisAssert(o->encoding == REDIS_ENCODING_HT);
105
106 de = dictFind(o->ptr, field);
107 if (de == NULL) return -1;
108 *value = dictGetVal(de);
109 return 0;
110}
111
112/* Higher level function of hashTypeGet*() that always returns a Redis
113 * object (either new or with refcount incremented), so that the caller
114 * can retain a reference or call decrRefCount after the usage.
115 *
116 * The lower level function can prevent copy on write so it is
117 * the preferred way of doing read operations. */
118robj *hashTypeGetObject(robj *o, robj *field) {
119 robj *value = NULL;
120
121 if (o->encoding == REDIS_ENCODING_ZIPLIST) {
122 unsigned char *vstr = NULL;
123 unsigned int vlen = UINT_MAX;
124 long long vll = LLONG_MAX;
125
126 if (hashTypeGetFromZiplist(o, field, &vstr, &vlen, &vll) == 0) {
127 if (vstr) {
128 value = createStringObject((char*)vstr, vlen);
129 } else {
130 value = createStringObjectFromLongLong(vll);
131 }
132 }
133
134 } else if (o->encoding == REDIS_ENCODING_HT) {
135 robj *aux;
136
137 if (hashTypeGetFromHashTable(o, field, &aux) == 0) {
138 incrRefCount(aux);
139 value = aux;
140 }
141 } else {
142 redisPanic("Unknown hash encoding");
143 }
144 return value;
145}
146
147/* Test if the specified field exists in the given hash. Returns 1 if the field
148 * exists, and 0 when it doesn't. */
149int hashTypeExists(robj *o, robj *field) {
150 if (o->encoding == REDIS_ENCODING_ZIPLIST) {
151 unsigned char *vstr = NULL;
152 unsigned int vlen = UINT_MAX;
153 long long vll = LLONG_MAX;
154
155 if (hashTypeGetFromZiplist(o, field, &vstr, &vlen, &vll) == 0) return 1;
156 } else if (o->encoding == REDIS_ENCODING_HT) {
157 robj *aux;
158
159 if (hashTypeGetFromHashTable(o, field, &aux) == 0) return 1;
160 } else {
161 redisPanic("Unknown hash encoding");
162 }
163 return 0;
164}
165
166/* Add an element, discard the old if the key already exists.
167 * Return 0 on insert and 1 on update.
168 * This function will take care of incrementing the reference count of the
169 * retained fields and value objects. */
170int hashTypeSet(robj *o, robj *field, robj *value) {
171 int update = 0;
172
173 if (o->encoding == REDIS_ENCODING_ZIPLIST) {
174 unsigned char *zl, *fptr, *vptr;
175
176 field = getDecodedObject(field);
177 value = getDecodedObject(value);
178
179 zl = o->ptr;
180 fptr = ziplistIndex(zl, ZIPLIST_HEAD);
181 if (fptr != NULL) {
182 fptr = ziplistFind(fptr, field->ptr, sdslen(field->ptr), 1);
183 if (fptr != NULL) {
184 /* Grab pointer to the value (fptr points to the field) */
185 vptr = ziplistNext(zl, fptr);
186 redisAssert(vptr != NULL);
187 update = 1;
188
189 /* Delete value */
190 zl = ziplistDelete(zl, &vptr);
191
192 /* Insert new value */
193 zl = ziplistInsert(zl, vptr, value->ptr, sdslen(value->ptr));
194 }
195 }
196
197 if (!update) {
198 /* Push new field/value pair onto the tail of the ziplist */
199 zl = ziplistPush(zl, field->ptr, sdslen(field->ptr), ZIPLIST_TAIL);
200 zl = ziplistPush(zl, value->ptr, sdslen(value->ptr), ZIPLIST_TAIL);
201 }
202 o->ptr = zl;
203 decrRefCount(field);
204 decrRefCount(value);
205
206 /* Check if the ziplist needs to be converted to a hash table */
207 if (hashTypeLength(o) > server.hash_max_ziplist_entries)
208 hashTypeConvert(o, REDIS_ENCODING_HT);
209 } else if (o->encoding == REDIS_ENCODING_HT) {
210 if (dictReplace(o->ptr, field, value)) { /* Insert */
211 incrRefCount(field);
212 } else { /* Update */
213 update = 1;
214 }
215 incrRefCount(value);
216 } else {
217 redisPanic("Unknown hash encoding");
218 }
219 return update;
220}
221
222/* Delete an element from a hash.
223 * Return 1 on deleted and 0 on not found. */
224int hashTypeDelete(robj *o, robj *field) {
225 int deleted = 0;
226
227 if (o->encoding == REDIS_ENCODING_ZIPLIST) {
228 unsigned char *zl, *fptr;
229
230 field = getDecodedObject(field);
231
232 zl = o->ptr;
233 fptr = ziplistIndex(zl, ZIPLIST_HEAD);
234 if (fptr != NULL) {
235 fptr = ziplistFind(fptr, field->ptr, sdslen(field->ptr), 1);
236 if (fptr != NULL) {
237 zl = ziplistDelete(zl,&fptr);
238 zl = ziplistDelete(zl,&fptr);
239 o->ptr = zl;
240 deleted = 1;
241 }
242 }
243
244 decrRefCount(field);
245
246 } else if (o->encoding == REDIS_ENCODING_HT) {
247 if (dictDelete((dict*)o->ptr, field) == REDIS_OK) {
248 deleted = 1;
249
250 /* Always check if the dictionary needs a resize after a delete. */
251 if (htNeedsResize(o->ptr)) dictResize(o->ptr);
252 }
253
254 } else {
255 redisPanic("Unknown hash encoding");
256 }
257
258 return deleted;
259}
260
261/* Return the number of elements in a hash. */
262unsigned long hashTypeLength(robj *o) {
263 unsigned long length = ULONG_MAX;
264
265 if (o->encoding == REDIS_ENCODING_ZIPLIST) {
266 length = ziplistLen(o->ptr) / 2;
267 } else if (o->encoding == REDIS_ENCODING_HT) {
268 length = dictSize((dict*)o->ptr);
269 } else {
270 redisPanic("Unknown hash encoding");
271 }
272
273 return length;
274}
275
276hashTypeIterator *hashTypeInitIterator(robj *subject) {
277 hashTypeIterator *hi = zmalloc(sizeof(hashTypeIterator));
278 hi->subject = subject;
279 hi->encoding = subject->encoding;
280
281 if (hi->encoding == REDIS_ENCODING_ZIPLIST) {
282 hi->fptr = NULL;
283 hi->vptr = NULL;
284 } else if (hi->encoding == REDIS_ENCODING_HT) {
285 hi->di = dictGetIterator(subject->ptr);
286 } else {
287 redisPanic("Unknown hash encoding");
288 }
289
290 return hi;
291}
292
293void hashTypeReleaseIterator(hashTypeIterator *hi) {
294 if (hi->encoding == REDIS_ENCODING_HT) {
295 dictReleaseIterator(hi->di);
296 }
297
298 zfree(hi);
299}
300
301/* Move to the next entry in the hash. Return REDIS_OK when the next entry
302 * could be found and REDIS_ERR when the iterator reaches the end. */
303int hashTypeNext(hashTypeIterator *hi) {
304 if (hi->encoding == REDIS_ENCODING_ZIPLIST) {
305 unsigned char *zl;
306 unsigned char *fptr, *vptr;
307
308 zl = hi->subject->ptr;
309 fptr = hi->fptr;
310 vptr = hi->vptr;
311
312 if (fptr == NULL) {
313 /* Initialize cursor */
314 redisAssert(vptr == NULL);
315 fptr = ziplistIndex(zl, 0);
316 } else {
317 /* Advance cursor */
318 redisAssert(vptr != NULL);
319 fptr = ziplistNext(zl, vptr);
320 }
321 if (fptr == NULL) return REDIS_ERR;
322
323 /* Grab pointer to the value (fptr points to the field) */
324 vptr = ziplistNext(zl, fptr);
325 redisAssert(vptr != NULL);
326
327 /* fptr, vptr now point to the first or next pair */
328 hi->fptr = fptr;
329 hi->vptr = vptr;
330 } else if (hi->encoding == REDIS_ENCODING_HT) {
331 if ((hi->de = dictNext(hi->di)) == NULL) return REDIS_ERR;
332 } else {
333 redisPanic("Unknown hash encoding");
334 }
335 return REDIS_OK;
336}
337
338/* Get the field or value at iterator cursor, for an iterator on a hash value
339 * encoded as a ziplist. Prototype is similar to `hashTypeGetFromZiplist`. */
340void hashTypeCurrentFromZiplist(hashTypeIterator *hi, int what,
341 unsigned char **vstr,
342 unsigned int *vlen,
343 long long *vll)
344{
345 int ret;
346
347 redisAssert(hi->encoding == REDIS_ENCODING_ZIPLIST);
348
349 if (what & REDIS_HASH_KEY) {
350 ret = ziplistGet(hi->fptr, vstr, vlen, vll);
351 redisAssert(ret);
352 } else {
353 ret = ziplistGet(hi->vptr, vstr, vlen, vll);
354 redisAssert(ret);
355 }
356}
357
358/* Get the field or value at iterator cursor, for an iterator on a hash value
359 * encoded as a ziplist. Prototype is similar to `hashTypeGetFromHashTable`. */
360void hashTypeCurrentFromHashTable(hashTypeIterator *hi, int what, robj **dst) {
361 redisAssert(hi->encoding == REDIS_ENCODING_HT);
362
363 if (what & REDIS_HASH_KEY) {
364 *dst = dictGetKey(hi->de);
365 } else {
366 *dst = dictGetVal(hi->de);
367 }
368}
369
370/* A non copy-on-write friendly but higher level version of hashTypeCurrent*()
371 * that returns an object with incremented refcount (or a new object). It is up
372 * to the caller to decrRefCount() the object if no reference is retained. */
373robj *hashTypeCurrentObject(hashTypeIterator *hi, int what) {
374 robj *dst;
375
376 if (hi->encoding == REDIS_ENCODING_ZIPLIST) {
377 unsigned char *vstr = NULL;
378 unsigned int vlen = UINT_MAX;
379 long long vll = LLONG_MAX;
380
381 hashTypeCurrentFromZiplist(hi, what, &vstr, &vlen, &vll);
382 if (vstr) {
383 dst = createStringObject((char*)vstr, vlen);
384 } else {
385 dst = createStringObjectFromLongLong(vll);
386 }
387
388 } else if (hi->encoding == REDIS_ENCODING_HT) {
389 hashTypeCurrentFromHashTable(hi, what, &dst);
390 incrRefCount(dst);
391
392 } else {
393 redisPanic("Unknown hash encoding");
394 }
395
396 return dst;
397}
398
399robj *hashTypeLookupWriteOrCreate(redisClient *c, robj *key) {
400 robj *o = lookupKeyWrite(c->db,key);
401 if (o == NULL) {
402 o = createHashObject();
403 dbAdd(c->db,key,o);
404 } else {
405 if (o->type != REDIS_HASH) {
406 addReply(c,shared.wrongtypeerr);
407 return NULL;
408 }
409 }
410 return o;
411}
412
413void hashTypeConvertZiplist(robj *o, int enc) {
414 redisAssert(o->encoding == REDIS_ENCODING_ZIPLIST);
415
416 if (enc == REDIS_ENCODING_ZIPLIST) {
417 /* Nothing to do... */
418
419 } else if (enc == REDIS_ENCODING_HT) {
420 hashTypeIterator *hi;
421 dict *dict;
422 int ret;
423
424 hi = hashTypeInitIterator(o);
425 dict = dictCreate(&hashDictType, NULL);
426
427 while (hashTypeNext(hi) != REDIS_ERR) {
428 robj *field, *value;
429
430 field = hashTypeCurrentObject(hi, REDIS_HASH_KEY);
431 field = tryObjectEncoding(field);
432 value = hashTypeCurrentObject(hi, REDIS_HASH_VALUE);
433 value = tryObjectEncoding(value);
434 ret = dictAdd(dict, field, value);
435 if (ret != DICT_OK) {
436 redisLogHexDump(REDIS_WARNING,"ziplist with dup elements dump",
437 o->ptr,ziplistBlobLen(o->ptr));
438 redisAssert(ret == DICT_OK);
439 }
440 }
441
442 hashTypeReleaseIterator(hi);
443 zfree(o->ptr);
444
445 o->encoding = REDIS_ENCODING_HT;
446 o->ptr = dict;
447
448 } else {
449 redisPanic("Unknown hash encoding");
450 }
451}
452
453void hashTypeConvert(robj *o, int enc) {
454 if (o->encoding == REDIS_ENCODING_ZIPLIST) {
455 hashTypeConvertZiplist(o, enc);
456 } else if (o->encoding == REDIS_ENCODING_HT) {
457 redisPanic("Not implemented");
458 } else {
459 redisPanic("Unknown hash encoding");
460 }
461}
462
463/*-----------------------------------------------------------------------------
464 * Hash type commands
465 *----------------------------------------------------------------------------*/
466
467void hsetCommand(redisClient *c) {
468 int update;
469 robj *o;
470
471 if ((o = hashTypeLookupWriteOrCreate(c,c->argv[1])) == NULL) return;
472 hashTypeTryConversion(o,c->argv,2,3);
473 hashTypeTryObjectEncoding(o,&c->argv[2], &c->argv[3]);
474 update = hashTypeSet(o,c->argv[2],c->argv[3]);
475 addReply(c, update ? shared.czero : shared.cone);
476 signalModifiedKey(c->db,c->argv[1]);
477 server.dirty++;
478}
479
480void hsetnxCommand(redisClient *c) {
481 robj *o;
482 if ((o = hashTypeLookupWriteOrCreate(c,c->argv[1])) == NULL) return;
483 hashTypeTryConversion(o,c->argv,2,3);
484
485 if (hashTypeExists(o, c->argv[2])) {
486 addReply(c, shared.czero);
487 } else {
488 hashTypeTryObjectEncoding(o,&c->argv[2], &c->argv[3]);
489 hashTypeSet(o,c->argv[2],c->argv[3]);
490 addReply(c, shared.cone);
491 signalModifiedKey(c->db,c->argv[1]);
492 server.dirty++;
493 }
494}
495
496void hmsetCommand(redisClient *c) {
497 int i;
498 robj *o;
499
500 if ((c->argc % 2) == 1) {
501 addReplyError(c,"wrong number of arguments for HMSET");
502 return;
503 }
504
505 if ((o = hashTypeLookupWriteOrCreate(c,c->argv[1])) == NULL) return;
506 hashTypeTryConversion(o,c->argv,2,c->argc-1);
507 for (i = 2; i < c->argc; i += 2) {
508 hashTypeTryObjectEncoding(o,&c->argv[i], &c->argv[i+1]);
509 hashTypeSet(o,c->argv[i],c->argv[i+1]);
510 }
511 addReply(c, shared.ok);
512 signalModifiedKey(c->db,c->argv[1]);
513 server.dirty++;
514}
515
516void hincrbyCommand(redisClient *c) {
517 long long value, incr, oldvalue;
518 robj *o, *current, *new;
519
520 if (getLongLongFromObjectOrReply(c,c->argv[3],&incr,NULL) != REDIS_OK) return;
521 if ((o = hashTypeLookupWriteOrCreate(c,c->argv[1])) == NULL) return;
522 if ((current = hashTypeGetObject(o,c->argv[2])) != NULL) {
523 if (getLongLongFromObjectOrReply(c,current,&value,
524 "hash value is not an integer") != REDIS_OK) {
525 decrRefCount(current);
526 return;
527 }
528 decrRefCount(current);
529 } else {
530 value = 0;
531 }
532
533 oldvalue = value;
534 if ((incr < 0 && oldvalue < 0 && incr < (LLONG_MIN-oldvalue)) ||
535 (incr > 0 && oldvalue > 0 && incr > (LLONG_MAX-oldvalue))) {
536 addReplyError(c,"increment or decrement would overflow");
537 return;
538 }
539 value += incr;
540 new = createStringObjectFromLongLong(value);
541 hashTypeTryObjectEncoding(o,&c->argv[2],NULL);
542 hashTypeSet(o,c->argv[2],new);
543 decrRefCount(new);
544 addReplyLongLong(c,value);
545 signalModifiedKey(c->db,c->argv[1]);
546 server.dirty++;
547}
548
549void hincrbyfloatCommand(redisClient *c) {
550 double long value, incr;
551 robj *o, *current, *new, *aux;
552
553 if (getLongDoubleFromObjectOrReply(c,c->argv[3],&incr,NULL) != REDIS_OK) return;
554 if ((o = hashTypeLookupWriteOrCreate(c,c->argv[1])) == NULL) return;
555 if ((current = hashTypeGetObject(o,c->argv[2])) != NULL) {
556 if (getLongDoubleFromObjectOrReply(c,current,&value,
557 "hash value is not a valid float") != REDIS_OK) {
558 decrRefCount(current);
559 return;
560 }
561 decrRefCount(current);
562 } else {
563 value = 0;
564 }
565
566 value += incr;
567 new = createStringObjectFromLongDouble(value);
568 hashTypeTryObjectEncoding(o,&c->argv[2],NULL);
569 hashTypeSet(o,c->argv[2],new);
570 addReplyBulk(c,new);
571 signalModifiedKey(c->db,c->argv[1]);
572 server.dirty++;
573
574 /* Always replicate HINCRBYFLOAT as an HSET command with the final value
575 * in order to make sure that differences in float pricision or formatting
576 * will not create differences in replicas or after an AOF restart. */
577 aux = createStringObject("HSET",4);
578 rewriteClientCommandArgument(c,0,aux);
579 decrRefCount(aux);
580 rewriteClientCommandArgument(c,3,new);
581 decrRefCount(new);
582}
583
584static void addHashFieldToReply(redisClient *c, robj *o, robj *field) {
585 int ret;
586
587 if (o == NULL) {
588 addReply(c, shared.nullbulk);
589 return;
590 }
591
592 if (o->encoding == REDIS_ENCODING_ZIPLIST) {
593 unsigned char *vstr = NULL;
594 unsigned int vlen = UINT_MAX;
595 long long vll = LLONG_MAX;
596
597 ret = hashTypeGetFromZiplist(o, field, &vstr, &vlen, &vll);
598 if (ret < 0) {
599 addReply(c, shared.nullbulk);
600 } else {
601 if (vstr) {
602 addReplyBulkCBuffer(c, vstr, vlen);
603 } else {
604 addReplyBulkLongLong(c, vll);
605 }
606 }
607
608 } else if (o->encoding == REDIS_ENCODING_HT) {
609 robj *value;
610
611 ret = hashTypeGetFromHashTable(o, field, &value);
612 if (ret < 0) {
613 addReply(c, shared.nullbulk);
614 } else {
615 addReplyBulk(c, value);
616 }
617
618 } else {
619 redisPanic("Unknown hash encoding");
620 }
621}
622
623void hgetCommand(redisClient *c) {
624 robj *o;
625
626 if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.nullbulk)) == NULL ||
627 checkType(c,o,REDIS_HASH)) return;
628
629 addHashFieldToReply(c, o, c->argv[2]);
630}
631
632void hmgetCommand(redisClient *c) {
633 robj *o;
634 int i;
635
636 /* Don't abort when the key cannot be found. Non-existing keys are empty
637 * hashes, where HMGET should respond with a series of null bulks. */
638 o = lookupKeyRead(c->db, c->argv[1]);
639 if (o != NULL && o->type != REDIS_HASH) {
640 addReply(c, shared.wrongtypeerr);
641 return;
642 }
643
644 addReplyMultiBulkLen(c, c->argc-2);
645 for (i = 2; i < c->argc; i++) {
646 addHashFieldToReply(c, o, c->argv[i]);
647 }
648}
649
650void hdelCommand(redisClient *c) {
651 robj *o;
652 int j, deleted = 0;
653
654 if ((o = lookupKeyWriteOrReply(c,c->argv[1],shared.czero)) == NULL ||
655 checkType(c,o,REDIS_HASH)) return;
656
657 for (j = 2; j < c->argc; j++) {
658 if (hashTypeDelete(o,c->argv[j])) {
659 deleted++;
660 if (hashTypeLength(o) == 0) {
661 dbDelete(c->db,c->argv[1]);
662 break;
663 }
664 }
665 }
666 if (deleted) {
667 signalModifiedKey(c->db,c->argv[1]);
668 server.dirty += deleted;
669 }
670 addReplyLongLong(c,deleted);
671}
672
673void hlenCommand(redisClient *c) {
674 robj *o;
675 if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL ||
676 checkType(c,o,REDIS_HASH)) return;
677
678 addReplyLongLong(c,hashTypeLength(o));
679}
680
681static void addHashIteratorCursorToReply(redisClient *c, hashTypeIterator *hi, int what) {
682 if (hi->encoding == REDIS_ENCODING_ZIPLIST) {
683 unsigned char *vstr = NULL;
684 unsigned int vlen = UINT_MAX;
685 long long vll = LLONG_MAX;
686
687 hashTypeCurrentFromZiplist(hi, what, &vstr, &vlen, &vll);
688 if (vstr) {
689 addReplyBulkCBuffer(c, vstr, vlen);
690 } else {
691 addReplyBulkLongLong(c, vll);
692 }
693
694 } else if (hi->encoding == REDIS_ENCODING_HT) {
695 robj *value;
696
697 hashTypeCurrentFromHashTable(hi, what, &value);
698 addReplyBulk(c, value);
699
700 } else {
701 redisPanic("Unknown hash encoding");
702 }
703}
704
705void genericHgetallCommand(redisClient *c, int flags) {
706 robj *o;
707 hashTypeIterator *hi;
708 int multiplier = 0;
709 int length, count = 0;
710
711 if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.emptymultibulk)) == NULL
712 || checkType(c,o,REDIS_HASH)) return;
713
714 if (flags & REDIS_HASH_KEY) multiplier++;
715 if (flags & REDIS_HASH_VALUE) multiplier++;
716
717 length = hashTypeLength(o) * multiplier;
718 addReplyMultiBulkLen(c, length);
719
720 hi = hashTypeInitIterator(o);
721 while (hashTypeNext(hi) != REDIS_ERR) {
722 if (flags & REDIS_HASH_KEY) {
723 addHashIteratorCursorToReply(c, hi, REDIS_HASH_KEY);
724 count++;
725 }
726 if (flags & REDIS_HASH_VALUE) {
727 addHashIteratorCursorToReply(c, hi, REDIS_HASH_VALUE);
728 count++;
729 }
730 }
731
732 hashTypeReleaseIterator(hi);
733 redisAssert(count == length);
734}
735
736void hkeysCommand(redisClient *c) {
737 genericHgetallCommand(c,REDIS_HASH_KEY);
738}
739
740void hvalsCommand(redisClient *c) {
741 genericHgetallCommand(c,REDIS_HASH_VALUE);
742}
743
744void hgetallCommand(redisClient *c) {
745 genericHgetallCommand(c,REDIS_HASH_KEY|REDIS_HASH_VALUE);
746}
747
748void hexistsCommand(redisClient *c) {
749 robj *o;
750 if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL ||
751 checkType(c,o,REDIS_HASH)) return;
752
753 addReply(c, hashTypeExists(o,c->argv[2]) ? shared.cone : shared.czero);
754}