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. */ 12 void 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. */ 27 void 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. 35 * 36 * If the string is found either REDIS_ENCODING_HT or REDIS_ENCODING_ZIPMAP 37 * is returned, and either **objval or **v and *vlen are set accordingly, 38 * so that objects in hash tables are returend as objects and pointers 39 * inside a zipmap are returned as such. 40 * 41 * If the object was not found -1 is returned. 42 * 43 * This function is copy on write friendly as there is no incr/decr 44 * of refcount needed if objects are accessed just for reading operations. */ 45 int hashTypeGet(robj *o, robj *key, robj **objval, unsigned char **v, 46 unsigned int *vlen) 47 { 48 if (o->encoding == REDIS_ENCODING_ZIPMAP) { 49 int found; 50 51 key = getDecodedObject(key); 52 found = zipmapGet(o->ptr,key->ptr,sdslen(key->ptr),v,vlen); 53 decrRefCount(key); 54 if (!found) return -1; 55 } else { 56 dictEntry *de = dictFind(o->ptr,key); 57 if (de == NULL) return -1; 58 *objval = dictGetEntryVal(de); 59 } 60 return o->encoding; 61 } 62 63 /* Higher level function of hashTypeGet() that always returns a Redis 64 * object (either new or with refcount incremented), so that the caller 65 * can retain a reference or call decrRefCount after the usage. 66 * 67 * The lower level function can prevent copy on write so it is 68 * the preferred way of doing read operations. */ 69 robj *hashTypeGetObject(robj *o, robj *key) { 70 robj *objval; 71 unsigned char *v; 72 unsigned int vlen; 73 74 int encoding = hashTypeGet(o,key,&objval,&v,&vlen); 75 switch(encoding) { 76 case REDIS_ENCODING_HT: 77 incrRefCount(objval); 78 return objval; 79 case REDIS_ENCODING_ZIPMAP: 80 objval = createStringObject((char*)v,vlen); 81 return objval; 82 default: return NULL; 83 } 84 } 85 86 /* Test if the key exists in the given hash. Returns 1 if the key 87 * exists and 0 when it doesn't. */ 88 int hashTypeExists(robj *o, robj *key) { 89 if (o->encoding == REDIS_ENCODING_ZIPMAP) { 90 key = getDecodedObject(key); 91 if (zipmapExists(o->ptr,key->ptr,sdslen(key->ptr))) { 92 decrRefCount(key); 93 return 1; 94 } 95 decrRefCount(key); 96 } else { 97 if (dictFind(o->ptr,key) != NULL) { 98 return 1; 99 } 100 } 101 return 0; 102 } 103 104 /* Add an element, discard the old if the key already exists. 105 * Return 0 on insert and 1 on update. */ 106 int hashTypeSet(robj *o, robj *key, robj *value) { 107 int update = 0; 108 if (o->encoding == REDIS_ENCODING_ZIPMAP) { 109 key = getDecodedObject(key); 110 value = getDecodedObject(value); 111 o->ptr = zipmapSet(o->ptr, 112 key->ptr,sdslen(key->ptr), 113 value->ptr,sdslen(value->ptr), &update); 114 decrRefCount(key); 115 decrRefCount(value); 116 117 /* Check if the zipmap needs to be upgraded to a real hash table */ 118 if (zipmapLen(o->ptr) > server.hash_max_zipmap_entries) 119 convertToRealHash(o); 120 } else { 121 if (dictReplace(o->ptr,key,value)) { 122 /* Insert */ 123 incrRefCount(key); 124 } else { 125 /* Update */ 126 update = 1; 127 } 128 incrRefCount(value); 129 } 130 return update; 131 } 132 133 /* Delete an element from a hash. 134 * Return 1 on deleted and 0 on not found. */ 135 int hashTypeDelete(robj *o, robj *key) { 136 int deleted = 0; 137 if (o->encoding == REDIS_ENCODING_ZIPMAP) { 138 key = getDecodedObject(key); 139 o->ptr = zipmapDel(o->ptr,key->ptr,sdslen(key->ptr), &deleted); 140 decrRefCount(key); 141 } else { 142 deleted = dictDelete((dict*)o->ptr,key) == DICT_OK; 143 /* Always check if the dictionary needs a resize after a delete. */ 144 if (deleted && htNeedsResize(o->ptr)) dictResize(o->ptr); 145 } 146 return deleted; 147 } 148 149 /* Return the number of elements in a hash. */ 150 unsigned long hashTypeLength(robj *o) { 151 return (o->encoding == REDIS_ENCODING_ZIPMAP) ? 152 zipmapLen((unsigned char*)o->ptr) : dictSize((dict*)o->ptr); 153 } 154 155 hashTypeIterator *hashTypeInitIterator(robj *subject) { 156 hashTypeIterator *hi = zmalloc(sizeof(hashTypeIterator)); 157 hi->encoding = subject->encoding; 158 if (hi->encoding == REDIS_ENCODING_ZIPMAP) { 159 hi->zi = zipmapRewind(subject->ptr); 160 } else if (hi->encoding == REDIS_ENCODING_HT) { 161 hi->di = dictGetIterator(subject->ptr); 162 } else { 163 redisAssert(NULL); 164 } 165 return hi; 166 } 167 168 void hashTypeReleaseIterator(hashTypeIterator *hi) { 169 if (hi->encoding == REDIS_ENCODING_HT) { 170 dictReleaseIterator(hi->di); 171 } 172 zfree(hi); 173 } 174 175 /* Move to the next entry in the hash. Return REDIS_OK when the next entry 176 * could be found and REDIS_ERR when the iterator reaches the end. */ 177 int hashTypeNext(hashTypeIterator *hi) { 178 if (hi->encoding == REDIS_ENCODING_ZIPMAP) { 179 if ((hi->zi = zipmapNext(hi->zi, &hi->zk, &hi->zklen, 180 &hi->zv, &hi->zvlen)) == NULL) return REDIS_ERR; 181 } else { 182 if ((hi->de = dictNext(hi->di)) == NULL) return REDIS_ERR; 183 } 184 return REDIS_OK; 185 } 186 187 /* Get key or value object at current iteration position. 188 * The returned item differs with the hash object encoding: 189 * - When encoding is REDIS_ENCODING_HT, the objval pointer is populated 190 * with the original object. 191 * - When encoding is REDIS_ENCODING_ZIPMAP, a pointer to the string and 192 * its length is retunred populating the v and vlen pointers. 193 * This function is copy on write friendly as accessing objects in read only 194 * does not require writing to any memory page. 195 * 196 * The function returns the encoding of the object, so that the caller 197 * can underestand if the key or value was returned as object or C string. */ 198 int hashTypeCurrent(hashTypeIterator *hi, int what, robj **objval, unsigned char **v, unsigned int *vlen) { 199 if (hi->encoding == REDIS_ENCODING_ZIPMAP) { 200 if (what & REDIS_HASH_KEY) { 201 *v = hi->zk; 202 *vlen = hi->zklen; 203 } else { 204 *v = hi->zv; 205 *vlen = hi->zvlen; 206 } 207 } else { 208 if (what & REDIS_HASH_KEY) 209 *objval = dictGetEntryKey(hi->de); 210 else 211 *objval = dictGetEntryVal(hi->de); 212 } 213 return hi->encoding; 214 } 215 216 /* A non copy-on-write friendly but higher level version of hashTypeCurrent() 217 * that always returns an object with refcount incremented by one (or a new 218 * object), so it's up to the caller to decrRefCount() the object if no 219 * reference is retained. */ 220 robj *hashTypeCurrentObject(hashTypeIterator *hi, int what) { 221 robj *obj; 222 unsigned char *v = NULL; 223 unsigned int vlen = 0; 224 int encoding = hashTypeCurrent(hi,what,&obj,&v,&vlen); 225 226 if (encoding == REDIS_ENCODING_HT) { 227 incrRefCount(obj); 228 return obj; 229 } else { 230 return createStringObject((char*)v,vlen); 231 } 232 } 233 234 robj *hashTypeLookupWriteOrCreate(redisClient *c, robj *key) { 235 robj *o = lookupKeyWrite(c->db,key); 236 if (o == NULL) { 237 o = createHashObject(); 238 dbAdd(c->db,key,o); 239 } else { 240 if (o->type != REDIS_HASH) { 241 addReply(c,shared.wrongtypeerr); 242 return NULL; 243 } 244 } 245 return o; 246 } 247 248 void convertToRealHash(robj *o) { 249 unsigned char *key, *val, *p, *zm = o->ptr; 250 unsigned int klen, vlen; 251 dict *dict = dictCreate(&hashDictType,NULL); 252 253 redisAssert(o->type == REDIS_HASH && o->encoding != REDIS_ENCODING_HT); 254 p = zipmapRewind(zm); 255 while((p = zipmapNext(p,&key,&klen,&val,&vlen)) != NULL) { 256 robj *keyobj, *valobj; 257 258 keyobj = createStringObject((char*)key,klen); 259 valobj = createStringObject((char*)val,vlen); 260 keyobj = tryObjectEncoding(keyobj); 261 valobj = tryObjectEncoding(valobj); 262 dictAdd(dict,keyobj,valobj); 263 } 264 o->encoding = REDIS_ENCODING_HT; 265 o->ptr = dict; 266 zfree(zm); 267 } 268 269 /*----------------------------------------------------------------------------- 270 * Hash type commands 271 *----------------------------------------------------------------------------*/ 272 273 void hsetCommand(redisClient *c) { 274 int update; 275 robj *o; 276 277 if ((o = hashTypeLookupWriteOrCreate(c,c->argv[1])) == NULL) return; 278 hashTypeTryConversion(o,c->argv,2,3); 279 hashTypeTryObjectEncoding(o,&c->argv[2], &c->argv[3]); 280 update = hashTypeSet(o,c->argv[2],c->argv[3]); 281 addReply(c, update ? shared.czero : shared.cone); 282 signalModifiedKey(c->db,c->argv[1]); 283 server.dirty++; 284 } 285 286 void hsetnxCommand(redisClient *c) { 287 robj *o; 288 if ((o = hashTypeLookupWriteOrCreate(c,c->argv[1])) == NULL) return; 289 hashTypeTryConversion(o,c->argv,2,3); 290 291 if (hashTypeExists(o, c->argv[2])) { 292 addReply(c, shared.czero); 293 } else { 294 hashTypeTryObjectEncoding(o,&c->argv[2], &c->argv[3]); 295 hashTypeSet(o,c->argv[2],c->argv[3]); 296 addReply(c, shared.cone); 297 signalModifiedKey(c->db,c->argv[1]); 298 server.dirty++; 299 } 300 } 301 302 void hmsetCommand(redisClient *c) { 303 int i; 304 robj *o; 305 306 if ((c->argc % 2) == 1) { 307 addReplyError(c,"wrong number of arguments for HMSET"); 308 return; 309 } 310 311 if ((o = hashTypeLookupWriteOrCreate(c,c->argv[1])) == NULL) return; 312 hashTypeTryConversion(o,c->argv,2,c->argc-1); 313 for (i = 2; i < c->argc; i += 2) { 314 hashTypeTryObjectEncoding(o,&c->argv[i], &c->argv[i+1]); 315 hashTypeSet(o,c->argv[i],c->argv[i+1]); 316 } 317 addReply(c, shared.ok); 318 signalModifiedKey(c->db,c->argv[1]); 319 server.dirty++; 320 } 321 322 void hincrbyCommand(redisClient *c) { 323 long long value, incr; 324 robj *o, *current, *new; 325 326 if (getLongLongFromObjectOrReply(c,c->argv[3],&incr,NULL) != REDIS_OK) return; 327 if ((o = hashTypeLookupWriteOrCreate(c,c->argv[1])) == NULL) return; 328 if ((current = hashTypeGetObject(o,c->argv[2])) != NULL) { 329 if (getLongLongFromObjectOrReply(c,current,&value, 330 "hash value is not an integer") != REDIS_OK) { 331 decrRefCount(current); 332 return; 333 } 334 decrRefCount(current); 335 } else { 336 value = 0; 337 } 338 339 value += incr; 340 new = createStringObjectFromLongLong(value); 341 hashTypeTryObjectEncoding(o,&c->argv[2],NULL); 342 hashTypeSet(o,c->argv[2],new); 343 decrRefCount(new); 344 addReplyLongLong(c,value); 345 signalModifiedKey(c->db,c->argv[1]); 346 server.dirty++; 347 } 348 349 void hgetCommand(redisClient *c) { 350 robj *o, *value; 351 unsigned char *v; 352 unsigned int vlen; 353 int encoding; 354 355 if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.nullbulk)) == NULL || 356 checkType(c,o,REDIS_HASH)) return; 357 358 if ((encoding = hashTypeGet(o,c->argv[2],&value,&v,&vlen)) != -1) { 359 if (encoding == REDIS_ENCODING_HT) 360 addReplyBulk(c,value); 361 else 362 addReplyBulkCBuffer(c,v,vlen); 363 } else { 364 addReply(c,shared.nullbulk); 365 } 366 } 367 368 void hmgetCommand(redisClient *c) { 369 int i, encoding; 370 robj *o, *value; 371 unsigned char *v; 372 unsigned int vlen; 373 374 o = lookupKeyRead(c->db,c->argv[1]); 375 if (o != NULL && o->type != REDIS_HASH) { 376 addReply(c,shared.wrongtypeerr); 377 return; 378 } 379 380 /* Note the check for o != NULL happens inside the loop. This is 381 * done because objects that cannot be found are considered to be 382 * an empty hash. The reply should then be a series of NULLs. */ 383 addReplyMultiBulkLen(c,c->argc-2); 384 for (i = 2; i < c->argc; i++) { 385 if (o != NULL && 386 (encoding = hashTypeGet(o,c->argv[i],&value,&v,&vlen)) != -1) { 387 if (encoding == REDIS_ENCODING_HT) 388 addReplyBulk(c,value); 389 else 390 addReplyBulkCBuffer(c,v,vlen); 391 } else { 392 addReply(c,shared.nullbulk); 393 } 394 } 395 } 396 397 void hdelCommand(redisClient *c) { 398 robj *o; 399 if ((o = lookupKeyWriteOrReply(c,c->argv[1],shared.czero)) == NULL || 400 checkType(c,o,REDIS_HASH)) return; 401 402 if (hashTypeDelete(o,c->argv[2])) { 403 if (hashTypeLength(o) == 0) dbDelete(c->db,c->argv[1]); 404 addReply(c,shared.cone); 405 signalModifiedKey(c->db,c->argv[1]); 406 server.dirty++; 407 } else { 408 addReply(c,shared.czero); 409 } 410 } 411 412 void hlenCommand(redisClient *c) { 413 robj *o; 414 if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL || 415 checkType(c,o,REDIS_HASH)) return; 416 417 addReplyLongLong(c,hashTypeLength(o)); 418 } 419 420 void genericHgetallCommand(redisClient *c, int flags) { 421 robj *o; 422 unsigned long count = 0; 423 hashTypeIterator *hi; 424 void *replylen = NULL; 425 426 if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.emptymultibulk)) == NULL 427 || checkType(c,o,REDIS_HASH)) return; 428 429 replylen = addDeferredMultiBulkLength(c); 430 hi = hashTypeInitIterator(o); 431 while (hashTypeNext(hi) != REDIS_ERR) { 432 robj *obj; 433 unsigned char *v = NULL; 434 unsigned int vlen = 0; 435 int encoding; 436 437 if (flags & REDIS_HASH_KEY) { 438 encoding = hashTypeCurrent(hi,REDIS_HASH_KEY,&obj,&v,&vlen); 439 if (encoding == REDIS_ENCODING_HT) 440 addReplyBulk(c,obj); 441 else 442 addReplyBulkCBuffer(c,v,vlen); 443 count++; 444 } 445 if (flags & REDIS_HASH_VALUE) { 446 encoding = hashTypeCurrent(hi,REDIS_HASH_VALUE,&obj,&v,&vlen); 447 if (encoding == REDIS_ENCODING_HT) 448 addReplyBulk(c,obj); 449 else 450 addReplyBulkCBuffer(c,v,vlen); 451 count++; 452 } 453 } 454 hashTypeReleaseIterator(hi); 455 setDeferredMultiBulkLength(c,replylen,count); 456 } 457 458 void hkeysCommand(redisClient *c) { 459 genericHgetallCommand(c,REDIS_HASH_KEY); 460 } 461 462 void hvalsCommand(redisClient *c) { 463 genericHgetallCommand(c,REDIS_HASH_VALUE); 464 } 465 466 void hgetallCommand(redisClient *c) { 467 genericHgetallCommand(c,REDIS_HASH_KEY|REDIS_HASH_VALUE); 468 } 469 470 void hexistsCommand(redisClient *c) { 471 robj *o; 472 if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL || 473 checkType(c,o,REDIS_HASH)) return; 474 475 addReply(c, hashTypeExists(o,c->argv[2]) ? shared.cone : shared.czero); 476 } 477