| 1 | #include "redis.h" |
| 2 | #include <math.h> |
| 3 | #include <ctype.h> |
| 4 | |
| 5 | robj *createObject(int type, void *ptr) { |
| 6 | robj *o = zmalloc(sizeof(*o)); |
| 7 | o->type = type; |
| 8 | o->encoding = REDIS_ENCODING_RAW; |
| 9 | o->ptr = ptr; |
| 10 | o->refcount = 1; |
| 11 | |
| 12 | /* Set the LRU to the current lruclock (minutes resolution). */ |
| 13 | o->lru = server.lruclock; |
| 14 | return o; |
| 15 | } |
| 16 | |
| 17 | robj *createStringObject(char *ptr, size_t len) { |
| 18 | return createObject(REDIS_STRING,sdsnewlen(ptr,len)); |
| 19 | } |
| 20 | |
| 21 | robj *createStringObjectFromLongLong(long long value) { |
| 22 | robj *o; |
| 23 | if (value >= 0 && value < REDIS_SHARED_INTEGERS) { |
| 24 | incrRefCount(shared.integers[value]); |
| 25 | o = shared.integers[value]; |
| 26 | } else { |
| 27 | if (value >= LONG_MIN && value <= LONG_MAX) { |
| 28 | o = createObject(REDIS_STRING, NULL); |
| 29 | o->encoding = REDIS_ENCODING_INT; |
| 30 | o->ptr = (void*)((long)value); |
| 31 | } else { |
| 32 | o = createObject(REDIS_STRING,sdsfromlonglong(value)); |
| 33 | } |
| 34 | } |
| 35 | return o; |
| 36 | } |
| 37 | |
| 38 | /* Note: this function is defined into object.c since here it is where it |
| 39 | * belongs but it is actually designed to be used just for INCRBYFLOAT */ |
| 40 | robj *createStringObjectFromLongDouble(long double value) { |
| 41 | char buf[256]; |
| 42 | int len; |
| 43 | |
| 44 | /* We use 17 digits precision since with 128 bit floats that precision |
| 45 | * after rouding is able to represent most small decimal numbers in a way |
| 46 | * that is "non surprising" for the user (that is, most small decimal |
| 47 | * numbers will be represented in a way that when converted back into |
| 48 | * a string are exactly the same as what the user typed.) */ |
| 49 | len = snprintf(buf,sizeof(buf),"%.17Lf", value); |
| 50 | /* Now remove trailing zeroes after the '.' */ |
| 51 | if (strchr(buf,'.') != NULL) { |
| 52 | char *p = buf+len-1; |
| 53 | while(*p == '0') { |
| 54 | p--; |
| 55 | len--; |
| 56 | } |
| 57 | if (*p == '.') len--; |
| 58 | } |
| 59 | return createStringObject(buf,len); |
| 60 | } |
| 61 | |
| 62 | robj *dupStringObject(robj *o) { |
| 63 | redisAssertWithInfo(NULL,o,o->encoding == REDIS_ENCODING_RAW); |
| 64 | return createStringObject(o->ptr,sdslen(o->ptr)); |
| 65 | } |
| 66 | |
| 67 | robj *createListObject(void) { |
| 68 | list *l = listCreate(); |
| 69 | robj *o = createObject(REDIS_LIST,l); |
| 70 | listSetFreeMethod(l,decrRefCount); |
| 71 | o->encoding = REDIS_ENCODING_LINKEDLIST; |
| 72 | return o; |
| 73 | } |
| 74 | |
| 75 | robj *createZiplistObject(void) { |
| 76 | unsigned char *zl = ziplistNew(); |
| 77 | robj *o = createObject(REDIS_LIST,zl); |
| 78 | o->encoding = REDIS_ENCODING_ZIPLIST; |
| 79 | return o; |
| 80 | } |
| 81 | |
| 82 | robj *createSetObject(void) { |
| 83 | dict *d = dictCreate(&setDictType,NULL); |
| 84 | robj *o = createObject(REDIS_SET,d); |
| 85 | o->encoding = REDIS_ENCODING_HT; |
| 86 | return o; |
| 87 | } |
| 88 | |
| 89 | robj *createIntsetObject(void) { |
| 90 | intset *is = intsetNew(); |
| 91 | robj *o = createObject(REDIS_SET,is); |
| 92 | o->encoding = REDIS_ENCODING_INTSET; |
| 93 | return o; |
| 94 | } |
| 95 | |
| 96 | robj *createHashObject(void) { |
| 97 | unsigned char *zl = ziplistNew(); |
| 98 | robj *o = createObject(REDIS_HASH, zl); |
| 99 | o->encoding = REDIS_ENCODING_ZIPLIST; |
| 100 | return o; |
| 101 | } |
| 102 | |
| 103 | robj *createZsetObject(void) { |
| 104 | zset *zs = zmalloc(sizeof(*zs)); |
| 105 | robj *o; |
| 106 | |
| 107 | zs->dict = dictCreate(&zsetDictType,NULL); |
| 108 | zs->zsl = zslCreate(); |
| 109 | o = createObject(REDIS_ZSET,zs); |
| 110 | o->encoding = REDIS_ENCODING_SKIPLIST; |
| 111 | return o; |
| 112 | } |
| 113 | |
| 114 | robj *createZsetZiplistObject(void) { |
| 115 | unsigned char *zl = ziplistNew(); |
| 116 | robj *o = createObject(REDIS_ZSET,zl); |
| 117 | o->encoding = REDIS_ENCODING_ZIPLIST; |
| 118 | return o; |
| 119 | } |
| 120 | |
| 121 | void freeStringObject(robj *o) { |
| 122 | if (o->encoding == REDIS_ENCODING_RAW) { |
| 123 | sdsfree(o->ptr); |
| 124 | } |
| 125 | } |
| 126 | |
| 127 | void freeListObject(robj *o) { |
| 128 | switch (o->encoding) { |
| 129 | case REDIS_ENCODING_LINKEDLIST: |
| 130 | listRelease((list*) o->ptr); |
| 131 | break; |
| 132 | case REDIS_ENCODING_ZIPLIST: |
| 133 | zfree(o->ptr); |
| 134 | break; |
| 135 | default: |
| 136 | redisPanic("Unknown list encoding type"); |
| 137 | } |
| 138 | } |
| 139 | |
| 140 | void freeSetObject(robj *o) { |
| 141 | switch (o->encoding) { |
| 142 | case REDIS_ENCODING_HT: |
| 143 | dictRelease((dict*) o->ptr); |
| 144 | break; |
| 145 | case REDIS_ENCODING_INTSET: |
| 146 | zfree(o->ptr); |
| 147 | break; |
| 148 | default: |
| 149 | redisPanic("Unknown set encoding type"); |
| 150 | } |
| 151 | } |
| 152 | |
| 153 | void freeZsetObject(robj *o) { |
| 154 | zset *zs; |
| 155 | switch (o->encoding) { |
| 156 | case REDIS_ENCODING_SKIPLIST: |
| 157 | zs = o->ptr; |
| 158 | dictRelease(zs->dict); |
| 159 | zslFree(zs->zsl); |
| 160 | zfree(zs); |
| 161 | break; |
| 162 | case REDIS_ENCODING_ZIPLIST: |
| 163 | zfree(o->ptr); |
| 164 | break; |
| 165 | default: |
| 166 | redisPanic("Unknown sorted set encoding"); |
| 167 | } |
| 168 | } |
| 169 | |
| 170 | void freeHashObject(robj *o) { |
| 171 | switch (o->encoding) { |
| 172 | case REDIS_ENCODING_HT: |
| 173 | dictRelease((dict*) o->ptr); |
| 174 | break; |
| 175 | case REDIS_ENCODING_ZIPLIST: |
| 176 | zfree(o->ptr); |
| 177 | break; |
| 178 | default: |
| 179 | redisPanic("Unknown hash encoding type"); |
| 180 | break; |
| 181 | } |
| 182 | } |
| 183 | |
| 184 | void incrRefCount(robj *o) { |
| 185 | o->refcount++; |
| 186 | } |
| 187 | |
| 188 | void decrRefCount(void *obj) { |
| 189 | robj *o = obj; |
| 190 | |
| 191 | if (o->refcount <= 0) redisPanic("decrRefCount against refcount <= 0"); |
| 192 | if (o->refcount == 1) { |
| 193 | switch(o->type) { |
| 194 | case REDIS_STRING: freeStringObject(o); break; |
| 195 | case REDIS_LIST: freeListObject(o); break; |
| 196 | case REDIS_SET: freeSetObject(o); break; |
| 197 | case REDIS_ZSET: freeZsetObject(o); break; |
| 198 | case REDIS_HASH: freeHashObject(o); break; |
| 199 | default: redisPanic("Unknown object type"); break; |
| 200 | } |
| 201 | zfree(o); |
| 202 | } else { |
| 203 | o->refcount--; |
| 204 | } |
| 205 | } |
| 206 | |
| 207 | /* This function set the ref count to zero without freeing the object. |
| 208 | * It is useful in order to pass a new object to functions incrementing |
| 209 | * the ref count of the received object. Example: |
| 210 | * |
| 211 | * functionThatWillIncrementRefCount(resetRefCount(CreateObject(...))); |
| 212 | * |
| 213 | * Otherwise you need to resort to the less elegant pattern: |
| 214 | * |
| 215 | * *obj = createObject(...); |
| 216 | * functionThatWillIncrementRefCount(obj); |
| 217 | * decrRefCount(obj); |
| 218 | */ |
| 219 | robj *resetRefCount(robj *obj) { |
| 220 | obj->refcount = 0; |
| 221 | return obj; |
| 222 | } |
| 223 | |
| 224 | int checkType(redisClient *c, robj *o, int type) { |
| 225 | if (o->type != type) { |
| 226 | addReply(c,shared.wrongtypeerr); |
| 227 | return 1; |
| 228 | } |
| 229 | return 0; |
| 230 | } |
| 231 | |
| 232 | int isObjectRepresentableAsLongLong(robj *o, long long *llval) { |
| 233 | redisAssertWithInfo(NULL,o,o->type == REDIS_STRING); |
| 234 | if (o->encoding == REDIS_ENCODING_INT) { |
| 235 | if (llval) *llval = (long) o->ptr; |
| 236 | return REDIS_OK; |
| 237 | } else { |
| 238 | return string2ll(o->ptr,sdslen(o->ptr),llval) ? REDIS_OK : REDIS_ERR; |
| 239 | } |
| 240 | } |
| 241 | |
| 242 | /* Try to encode a string object in order to save space */ |
| 243 | robj *tryObjectEncoding(robj *o) { |
| 244 | long value; |
| 245 | sds s = o->ptr; |
| 246 | |
| 247 | if (o->encoding != REDIS_ENCODING_RAW) |
| 248 | return o; /* Already encoded */ |
| 249 | |
| 250 | /* It's not safe to encode shared objects: shared objects can be shared |
| 251 | * everywhere in the "object space" of Redis. Encoded objects can only |
| 252 | * appear as "values" (and not, for instance, as keys) */ |
| 253 | if (o->refcount > 1) return o; |
| 254 | |
| 255 | /* Currently we try to encode only strings */ |
| 256 | redisAssertWithInfo(NULL,o,o->type == REDIS_STRING); |
| 257 | |
| 258 | /* Check if we can represent this string as a long integer */ |
| 259 | if (!string2l(s,sdslen(s),&value)) return o; |
| 260 | |
| 261 | /* Ok, this object can be encoded... |
| 262 | * |
| 263 | * Can I use a shared object? Only if the object is inside a given range |
| 264 | * |
| 265 | * Note that we also avoid using shared integers when maxmemory is used |
| 266 | * because every object needs to have a private LRU field for the LRU |
| 267 | * algorithm to work well. */ |
| 268 | if (server.maxmemory == 0 && value >= 0 && value < REDIS_SHARED_INTEGERS) { |
| 269 | decrRefCount(o); |
| 270 | incrRefCount(shared.integers[value]); |
| 271 | return shared.integers[value]; |
| 272 | } else { |
| 273 | o->encoding = REDIS_ENCODING_INT; |
| 274 | sdsfree(o->ptr); |
| 275 | o->ptr = (void*) value; |
| 276 | return o; |
| 277 | } |
| 278 | } |
| 279 | |
| 280 | /* Get a decoded version of an encoded object (returned as a new object). |
| 281 | * If the object is already raw-encoded just increment the ref count. */ |
| 282 | robj *getDecodedObject(robj *o) { |
| 283 | robj *dec; |
| 284 | |
| 285 | if (o->encoding == REDIS_ENCODING_RAW) { |
| 286 | incrRefCount(o); |
| 287 | return o; |
| 288 | } |
| 289 | if (o->type == REDIS_STRING && o->encoding == REDIS_ENCODING_INT) { |
| 290 | char buf[32]; |
| 291 | |
| 292 | ll2string(buf,32,(long)o->ptr); |
| 293 | dec = createStringObject(buf,strlen(buf)); |
| 294 | return dec; |
| 295 | } else { |
| 296 | redisPanic("Unknown encoding type"); |
| 297 | } |
| 298 | } |
| 299 | |
| 300 | /* Compare two string objects via strcmp() or alike. |
| 301 | * Note that the objects may be integer-encoded. In such a case we |
| 302 | * use ll2string() to get a string representation of the numbers on the stack |
| 303 | * and compare the strings, it's much faster than calling getDecodedObject(). |
| 304 | * |
| 305 | * Important note: if objects are not integer encoded, but binary-safe strings, |
| 306 | * sdscmp() from sds.c will apply memcmp() so this function ca be considered |
| 307 | * binary safe. */ |
| 308 | int compareStringObjects(robj *a, robj *b) { |
| 309 | redisAssertWithInfo(NULL,a,a->type == REDIS_STRING && b->type == REDIS_STRING); |
| 310 | char bufa[128], bufb[128], *astr, *bstr; |
| 311 | int bothsds = 1; |
| 312 | |
| 313 | if (a == b) return 0; |
| 314 | if (a->encoding != REDIS_ENCODING_RAW) { |
| 315 | ll2string(bufa,sizeof(bufa),(long) a->ptr); |
| 316 | astr = bufa; |
| 317 | bothsds = 0; |
| 318 | } else { |
| 319 | astr = a->ptr; |
| 320 | } |
| 321 | if (b->encoding != REDIS_ENCODING_RAW) { |
| 322 | ll2string(bufb,sizeof(bufb),(long) b->ptr); |
| 323 | bstr = bufb; |
| 324 | bothsds = 0; |
| 325 | } else { |
| 326 | bstr = b->ptr; |
| 327 | } |
| 328 | return bothsds ? sdscmp(astr,bstr) : strcmp(astr,bstr); |
| 329 | } |
| 330 | |
| 331 | /* Equal string objects return 1 if the two objects are the same from the |
| 332 | * point of view of a string comparison, otherwise 0 is returned. Note that |
| 333 | * this function is faster then checking for (compareStringObject(a,b) == 0) |
| 334 | * because it can perform some more optimization. */ |
| 335 | int equalStringObjects(robj *a, robj *b) { |
| 336 | if (a->encoding != REDIS_ENCODING_RAW && b->encoding != REDIS_ENCODING_RAW){ |
| 337 | return a->ptr == b->ptr; |
| 338 | } else { |
| 339 | return compareStringObjects(a,b) == 0; |
| 340 | } |
| 341 | } |
| 342 | |
| 343 | size_t stringObjectLen(robj *o) { |
| 344 | redisAssertWithInfo(NULL,o,o->type == REDIS_STRING); |
| 345 | if (o->encoding == REDIS_ENCODING_RAW) { |
| 346 | return sdslen(o->ptr); |
| 347 | } else { |
| 348 | char buf[32]; |
| 349 | |
| 350 | return ll2string(buf,32,(long)o->ptr); |
| 351 | } |
| 352 | } |
| 353 | |
| 354 | int getDoubleFromObject(robj *o, double *target) { |
| 355 | double value; |
| 356 | char *eptr; |
| 357 | |
| 358 | if (o == NULL) { |
| 359 | value = 0; |
| 360 | } else { |
| 361 | redisAssertWithInfo(NULL,o,o->type == REDIS_STRING); |
| 362 | if (o->encoding == REDIS_ENCODING_RAW) { |
| 363 | errno = 0; |
| 364 | value = strtod(o->ptr, &eptr); |
| 365 | if (isspace(((char*)o->ptr)[0]) || eptr[0] != '\0' || |
| 366 | errno == ERANGE || isnan(value)) |
| 367 | return REDIS_ERR; |
| 368 | } else if (o->encoding == REDIS_ENCODING_INT) { |
| 369 | value = (long)o->ptr; |
| 370 | } else { |
| 371 | redisPanic("Unknown string encoding"); |
| 372 | } |
| 373 | } |
| 374 | *target = value; |
| 375 | return REDIS_OK; |
| 376 | } |
| 377 | |
| 378 | int getDoubleFromObjectOrReply(redisClient *c, robj *o, double *target, const char *msg) { |
| 379 | double value; |
| 380 | if (getDoubleFromObject(o, &value) != REDIS_OK) { |
| 381 | if (msg != NULL) { |
| 382 | addReplyError(c,(char*)msg); |
| 383 | } else { |
| 384 | addReplyError(c,"value is not a valid float"); |
| 385 | } |
| 386 | return REDIS_ERR; |
| 387 | } |
| 388 | *target = value; |
| 389 | return REDIS_OK; |
| 390 | } |
| 391 | |
| 392 | int getLongDoubleFromObject(robj *o, long double *target) { |
| 393 | long double value; |
| 394 | char *eptr; |
| 395 | |
| 396 | if (o == NULL) { |
| 397 | value = 0; |
| 398 | } else { |
| 399 | redisAssertWithInfo(NULL,o,o->type == REDIS_STRING); |
| 400 | if (o->encoding == REDIS_ENCODING_RAW) { |
| 401 | errno = 0; |
| 402 | value = strtold(o->ptr, &eptr); |
| 403 | if (isspace(((char*)o->ptr)[0]) || eptr[0] != '\0' || |
| 404 | errno == ERANGE || isnan(value)) |
| 405 | return REDIS_ERR; |
| 406 | } else if (o->encoding == REDIS_ENCODING_INT) { |
| 407 | value = (long)o->ptr; |
| 408 | } else { |
| 409 | redisPanic("Unknown string encoding"); |
| 410 | } |
| 411 | } |
| 412 | *target = value; |
| 413 | return REDIS_OK; |
| 414 | } |
| 415 | |
| 416 | int getLongDoubleFromObjectOrReply(redisClient *c, robj *o, long double *target, const char *msg) { |
| 417 | long double value; |
| 418 | if (getLongDoubleFromObject(o, &value) != REDIS_OK) { |
| 419 | if (msg != NULL) { |
| 420 | addReplyError(c,(char*)msg); |
| 421 | } else { |
| 422 | addReplyError(c,"value is not a valid float"); |
| 423 | } |
| 424 | return REDIS_ERR; |
| 425 | } |
| 426 | *target = value; |
| 427 | return REDIS_OK; |
| 428 | } |
| 429 | |
| 430 | int getLongLongFromObject(robj *o, long long *target) { |
| 431 | long long value; |
| 432 | char *eptr; |
| 433 | |
| 434 | if (o == NULL) { |
| 435 | value = 0; |
| 436 | } else { |
| 437 | redisAssertWithInfo(NULL,o,o->type == REDIS_STRING); |
| 438 | if (o->encoding == REDIS_ENCODING_RAW) { |
| 439 | errno = 0; |
| 440 | value = strtoll(o->ptr, &eptr, 10); |
| 441 | if (isspace(((char*)o->ptr)[0]) || eptr[0] != '\0' || |
| 442 | errno == ERANGE) |
| 443 | return REDIS_ERR; |
| 444 | } else if (o->encoding == REDIS_ENCODING_INT) { |
| 445 | value = (long)o->ptr; |
| 446 | } else { |
| 447 | redisPanic("Unknown string encoding"); |
| 448 | } |
| 449 | } |
| 450 | if (target) *target = value; |
| 451 | return REDIS_OK; |
| 452 | } |
| 453 | |
| 454 | int getLongLongFromObjectOrReply(redisClient *c, robj *o, long long *target, const char *msg) { |
| 455 | long long value; |
| 456 | if (getLongLongFromObject(o, &value) != REDIS_OK) { |
| 457 | if (msg != NULL) { |
| 458 | addReplyError(c,(char*)msg); |
| 459 | } else { |
| 460 | addReplyError(c,"value is not an integer or out of range"); |
| 461 | } |
| 462 | return REDIS_ERR; |
| 463 | } |
| 464 | *target = value; |
| 465 | return REDIS_OK; |
| 466 | } |
| 467 | |
| 468 | int getLongFromObjectOrReply(redisClient *c, robj *o, long *target, const char *msg) { |
| 469 | long long value; |
| 470 | |
| 471 | if (getLongLongFromObjectOrReply(c, o, &value, msg) != REDIS_OK) return REDIS_ERR; |
| 472 | if (value < LONG_MIN || value > LONG_MAX) { |
| 473 | if (msg != NULL) { |
| 474 | addReplyError(c,(char*)msg); |
| 475 | } else { |
| 476 | addReplyError(c,"value is out of range"); |
| 477 | } |
| 478 | return REDIS_ERR; |
| 479 | } |
| 480 | *target = value; |
| 481 | return REDIS_OK; |
| 482 | } |
| 483 | |
| 484 | char *strEncoding(int encoding) { |
| 485 | switch(encoding) { |
| 486 | case REDIS_ENCODING_RAW: return "raw"; |
| 487 | case REDIS_ENCODING_INT: return "int"; |
| 488 | case REDIS_ENCODING_HT: return "hashtable"; |
| 489 | case REDIS_ENCODING_LINKEDLIST: return "linkedlist"; |
| 490 | case REDIS_ENCODING_ZIPLIST: return "ziplist"; |
| 491 | case REDIS_ENCODING_INTSET: return "intset"; |
| 492 | case REDIS_ENCODING_SKIPLIST: return "skiplist"; |
| 493 | default: return "unknown"; |
| 494 | } |
| 495 | } |
| 496 | |
| 497 | /* Given an object returns the min number of seconds the object was never |
| 498 | * requested, using an approximated LRU algorithm. */ |
| 499 | unsigned long estimateObjectIdleTime(robj *o) { |
| 500 | if (server.lruclock >= o->lru) { |
| 501 | return (server.lruclock - o->lru) * REDIS_LRU_CLOCK_RESOLUTION; |
| 502 | } else { |
| 503 | return ((REDIS_LRU_CLOCK_MAX - o->lru) + server.lruclock) * |
| 504 | REDIS_LRU_CLOCK_RESOLUTION; |
| 505 | } |
| 506 | } |
| 507 | |
| 508 | /* This is an helper function for the DEBUG command. We need to lookup keys |
| 509 | * without any modification of LRU or other parameters. */ |
| 510 | robj *objectCommandLookup(redisClient *c, robj *key) { |
| 511 | dictEntry *de; |
| 512 | |
| 513 | if ((de = dictFind(c->db->dict,key->ptr)) == NULL) return NULL; |
| 514 | return (robj*) dictGetVal(de); |
| 515 | } |
| 516 | |
| 517 | robj *objectCommandLookupOrReply(redisClient *c, robj *key, robj *reply) { |
| 518 | robj *o = objectCommandLookup(c,key); |
| 519 | |
| 520 | if (!o) addReply(c, reply); |
| 521 | return o; |
| 522 | } |
| 523 | |
| 524 | /* Object command allows to inspect the internals of an Redis Object. |
| 525 | * Usage: OBJECT <verb> ... arguments ... */ |
| 526 | void objectCommand(redisClient *c) { |
| 527 | robj *o; |
| 528 | |
| 529 | if (!strcasecmp(c->argv[1]->ptr,"refcount") && c->argc == 3) { |
| 530 | if ((o = objectCommandLookupOrReply(c,c->argv[2],shared.nullbulk)) |
| 531 | == NULL) return; |
| 532 | addReplyLongLong(c,o->refcount); |
| 533 | } else if (!strcasecmp(c->argv[1]->ptr,"encoding") && c->argc == 3) { |
| 534 | if ((o = objectCommandLookupOrReply(c,c->argv[2],shared.nullbulk)) |
| 535 | == NULL) return; |
| 536 | addReplyBulkCString(c,strEncoding(o->encoding)); |
| 537 | } else if (!strcasecmp(c->argv[1]->ptr,"idletime") && c->argc == 3) { |
| 538 | if ((o = objectCommandLookupOrReply(c,c->argv[2],shared.nullbulk)) |
| 539 | == NULL) return; |
| 540 | addReplyLongLong(c,estimateObjectIdleTime(o)); |
| 541 | } else { |
| 542 | addReplyError(c,"Syntax error. Try OBJECT (refcount|encoding|idletime)"); |
| 543 | } |
| 544 | } |
| 545 | |