xref: /redis-3.2.3/src/object.c (revision b9bb7e2d)
1 /* Redis Object implementation.
2  *
3  * Copyright (c) 2009-2012, Salvatore Sanfilippo <antirez at gmail dot com>
4  * All rights reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions are met:
8  *
9  *   * Redistributions of source code must retain the above copyright notice,
10  *     this list of conditions and the following disclaimer.
11  *   * Redistributions in binary form must reproduce the above copyright
12  *     notice, this list of conditions and the following disclaimer in the
13  *     documentation and/or other materials provided with the distribution.
14  *   * Neither the name of Redis nor the names of its contributors may be used
15  *     to endorse or promote products derived from this software without
16  *     specific prior written permission.
17  *
18  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
22  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
23  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
24  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
25  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
26  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28  * POSSIBILITY OF SUCH DAMAGE.
29  */
30 
31 #include "redis.h"
32 #include <math.h>
33 #include <ctype.h>
34 
35 #ifdef __CYGWIN__
36 #define strtold(a,b) ((long double)strtod((a),(b)))
37 #endif
38 
39 robj *createObject(int type, void *ptr) {
40     robj *o = zmalloc(sizeof(*o));
41     o->type = type;
42     o->encoding = REDIS_ENCODING_RAW;
43     o->ptr = ptr;
44     o->refcount = 1;
45 
46     /* Set the LRU to the current lruclock (minutes resolution). */
47     o->lru = LRU_CLOCK();
48     return o;
49 }
50 
51 /* Create a string object with encoding REDIS_ENCODING_RAW, that is a plain
52  * string object where o->ptr points to a proper sds string. */
53 robj *createRawStringObject(char *ptr, size_t len) {
54     return createObject(REDIS_STRING,sdsnewlen(ptr,len));
55 }
56 
57 /* Create a string object with encoding REDIS_ENCODING_EMBSTR, that is
58  * an object where the sds string is actually an unmodifiable string
59  * allocated in the same chunk as the object itself. */
60 robj *createEmbeddedStringObject(char *ptr, size_t len) {
61     robj *o = zmalloc(sizeof(robj)+sizeof(struct sdshdr)+len+1);
62     struct sdshdr *sh = (void*)(o+1);
63 
64     o->type = REDIS_STRING;
65     o->encoding = REDIS_ENCODING_EMBSTR;
66     o->ptr = sh+1;
67     o->refcount = 1;
68     o->lru = LRU_CLOCK();
69 
70     sh->len = len;
71     sh->free = 0;
72     if (ptr) {
73         memcpy(sh->buf,ptr,len);
74         sh->buf[len] = '\0';
75     } else {
76         memset(sh->buf,0,len+1);
77     }
78     return o;
79 }
80 
81 /* Create a string object with EMBSTR encoding if it is smaller than
82  * REIDS_ENCODING_EMBSTR_SIZE_LIMIT, otherwise the RAW encoding is
83  * used.
84  *
85  * The current limit of 39 is chosen so that the biggest string object
86  * we allocate as EMBSTR will still fit into the 64 byte arena of jemalloc. */
87 #define REDIS_ENCODING_EMBSTR_SIZE_LIMIT 39
88 robj *createStringObject(char *ptr, size_t len) {
89     if (len <= REDIS_ENCODING_EMBSTR_SIZE_LIMIT)
90         return createEmbeddedStringObject(ptr,len);
91     else
92         return createRawStringObject(ptr,len);
93 }
94 
95 robj *createStringObjectFromLongLong(long long value) {
96     robj *o;
97     if (value >= 0 && value < REDIS_SHARED_INTEGERS) {
98         incrRefCount(shared.integers[value]);
99         o = shared.integers[value];
100     } else {
101         if (value >= LONG_MIN && value <= LONG_MAX) {
102             o = createObject(REDIS_STRING, NULL);
103             o->encoding = REDIS_ENCODING_INT;
104             o->ptr = (void*)((long)value);
105         } else {
106             o = createObject(REDIS_STRING,sdsfromlonglong(value));
107         }
108     }
109     return o;
110 }
111 
112 /* Note: this function is defined into object.c since here it is where it
113  * belongs but it is actually designed to be used just for INCRBYFLOAT */
114 robj *createStringObjectFromLongDouble(long double value) {
115     char buf[256];
116     int len;
117 
118     /* We use 17 digits precision since with 128 bit floats that precision
119      * after rounding is able to represent most small decimal numbers in a way
120      * that is "non surprising" for the user (that is, most small decimal
121      * numbers will be represented in a way that when converted back into
122      * a string are exactly the same as what the user typed.) */
123     len = snprintf(buf,sizeof(buf),"%.17Lf", value);
124     /* Now remove trailing zeroes after the '.' */
125     if (strchr(buf,'.') != NULL) {
126         char *p = buf+len-1;
127         while(*p == '0') {
128             p--;
129             len--;
130         }
131         if (*p == '.') len--;
132     }
133     return createStringObject(buf,len);
134 }
135 
136 /* Duplicate a string object, with the guarantee that the returned object
137  * has the same encoding as the original one.
138  *
139  * This function also guarantees that duplicating a small integere object
140  * (or a string object that contains a representation of a small integer)
141  * will always result in a fresh object that is unshared (refcount == 1).
142  *
143  * The resulting object always has refcount set to 1. */
144 robj *dupStringObject(robj *o) {
145     robj *d;
146 
147     redisAssert(o->type == REDIS_STRING);
148 
149     switch(o->encoding) {
150     case REDIS_ENCODING_RAW:
151         return createRawStringObject(o->ptr,sdslen(o->ptr));
152     case REDIS_ENCODING_EMBSTR:
153         return createEmbeddedStringObject(o->ptr,sdslen(o->ptr));
154     case REDIS_ENCODING_INT:
155         d = createObject(REDIS_STRING, NULL);
156         d->encoding = REDIS_ENCODING_INT;
157         d->ptr = o->ptr;
158         return d;
159     default:
160         redisPanic("Wrong encoding.");
161         break;
162     }
163 }
164 
165 robj *createListObject(void) {
166     list *l = listCreate();
167     robj *o = createObject(REDIS_LIST,l);
168     listSetFreeMethod(l,decrRefCountVoid);
169     o->encoding = REDIS_ENCODING_LINKEDLIST;
170     return o;
171 }
172 
173 robj *createZiplistObject(void) {
174     unsigned char *zl = ziplistNew();
175     robj *o = createObject(REDIS_LIST,zl);
176     o->encoding = REDIS_ENCODING_ZIPLIST;
177     return o;
178 }
179 
180 robj *createSetObject(void) {
181     dict *d = dictCreate(&setDictType,NULL);
182     robj *o = createObject(REDIS_SET,d);
183     o->encoding = REDIS_ENCODING_HT;
184     return o;
185 }
186 
187 robj *createIntsetObject(void) {
188     intset *is = intsetNew();
189     robj *o = createObject(REDIS_SET,is);
190     o->encoding = REDIS_ENCODING_INTSET;
191     return o;
192 }
193 
194 robj *createHashObject(void) {
195     unsigned char *zl = ziplistNew();
196     robj *o = createObject(REDIS_HASH, zl);
197     o->encoding = REDIS_ENCODING_ZIPLIST;
198     return o;
199 }
200 
201 robj *createZsetObject(void) {
202     zset *zs = zmalloc(sizeof(*zs));
203     robj *o;
204 
205     zs->dict = dictCreate(&zsetDictType,NULL);
206     zs->zsl = zslCreate();
207     o = createObject(REDIS_ZSET,zs);
208     o->encoding = REDIS_ENCODING_SKIPLIST;
209     return o;
210 }
211 
212 robj *createZsetZiplistObject(void) {
213     unsigned char *zl = ziplistNew();
214     robj *o = createObject(REDIS_ZSET,zl);
215     o->encoding = REDIS_ENCODING_ZIPLIST;
216     return o;
217 }
218 
219 void freeStringObject(robj *o) {
220     if (o->encoding == REDIS_ENCODING_RAW) {
221         sdsfree(o->ptr);
222     }
223 }
224 
225 void freeListObject(robj *o) {
226     switch (o->encoding) {
227     case REDIS_ENCODING_LINKEDLIST:
228         listRelease((list*) o->ptr);
229         break;
230     case REDIS_ENCODING_ZIPLIST:
231         zfree(o->ptr);
232         break;
233     default:
234         redisPanic("Unknown list encoding type");
235     }
236 }
237 
238 void freeSetObject(robj *o) {
239     switch (o->encoding) {
240     case REDIS_ENCODING_HT:
241         dictRelease((dict*) o->ptr);
242         break;
243     case REDIS_ENCODING_INTSET:
244         zfree(o->ptr);
245         break;
246     default:
247         redisPanic("Unknown set encoding type");
248     }
249 }
250 
251 void freeZsetObject(robj *o) {
252     zset *zs;
253     switch (o->encoding) {
254     case REDIS_ENCODING_SKIPLIST:
255         zs = o->ptr;
256         dictRelease(zs->dict);
257         zslFree(zs->zsl);
258         zfree(zs);
259         break;
260     case REDIS_ENCODING_ZIPLIST:
261         zfree(o->ptr);
262         break;
263     default:
264         redisPanic("Unknown sorted set encoding");
265     }
266 }
267 
268 void freeHashObject(robj *o) {
269     switch (o->encoding) {
270     case REDIS_ENCODING_HT:
271         dictRelease((dict*) o->ptr);
272         break;
273     case REDIS_ENCODING_ZIPLIST:
274         zfree(o->ptr);
275         break;
276     default:
277         redisPanic("Unknown hash encoding type");
278         break;
279     }
280 }
281 
282 void incrRefCount(robj *o) {
283     o->refcount++;
284 }
285 
286 void decrRefCount(robj *o) {
287     if (o->refcount <= 0) redisPanic("decrRefCount against refcount <= 0");
288     if (o->refcount == 1) {
289         switch(o->type) {
290         case REDIS_STRING: freeStringObject(o); break;
291         case REDIS_LIST: freeListObject(o); break;
292         case REDIS_SET: freeSetObject(o); break;
293         case REDIS_ZSET: freeZsetObject(o); break;
294         case REDIS_HASH: freeHashObject(o); break;
295         default: redisPanic("Unknown object type"); break;
296         }
297         zfree(o);
298     } else {
299         o->refcount--;
300     }
301 }
302 
303 /* This variant of decrRefCount() gets its argument as void, and is useful
304  * as free method in data structures that expect a 'void free_object(void*)'
305  * prototype for the free method. */
306 void decrRefCountVoid(void *o) {
307     decrRefCount(o);
308 }
309 
310 /* This function set the ref count to zero without freeing the object.
311  * It is useful in order to pass a new object to functions incrementing
312  * the ref count of the received object. Example:
313  *
314  *    functionThatWillIncrementRefCount(resetRefCount(CreateObject(...)));
315  *
316  * Otherwise you need to resort to the less elegant pattern:
317  *
318  *    *obj = createObject(...);
319  *    functionThatWillIncrementRefCount(obj);
320  *    decrRefCount(obj);
321  */
322 robj *resetRefCount(robj *obj) {
323     obj->refcount = 0;
324     return obj;
325 }
326 
327 int checkType(redisClient *c, robj *o, int type) {
328     if (o->type != type) {
329         addReply(c,shared.wrongtypeerr);
330         return 1;
331     }
332     return 0;
333 }
334 
335 int isObjectRepresentableAsLongLong(robj *o, long long *llval) {
336     redisAssertWithInfo(NULL,o,o->type == REDIS_STRING);
337     if (o->encoding == REDIS_ENCODING_INT) {
338         if (llval) *llval = (long) o->ptr;
339         return REDIS_OK;
340     } else {
341         return string2ll(o->ptr,sdslen(o->ptr),llval) ? REDIS_OK : REDIS_ERR;
342     }
343 }
344 
345 /* Try to encode a string object in order to save space */
346 robj *tryObjectEncoding(robj *o) {
347     long value;
348     sds s = o->ptr;
349     size_t len;
350 
351     /* Make sure this is a string object, the only type we encode
352      * in this function. Other types use encoded memory efficient
353      * representations but are handled by the commands implementing
354      * the type. */
355     redisAssertWithInfo(NULL,o,o->type == REDIS_STRING);
356 
357     /* We try some specialized encoding only for objects that are
358      * RAW or EMBSTR encoded, in other words objects that are still
359      * in represented by an actually array of chars. */
360     if (!sdsEncodedObject(o)) return o;
361 
362     /* It's not safe to encode shared objects: shared objects can be shared
363      * everywhere in the "object space" of Redis and may end in places where
364      * they are not handled. We handle them only as values in the keyspace. */
365      if (o->refcount > 1) return o;
366 
367     /* Check if we can represent this string as a long integer.
368      * Note that we are sure that a string larger than 21 chars is not
369      * representable as a 32 nor 64 bit integer. */
370     len = sdslen(s);
371     if (len <= 21 && string2l(s,len,&value)) {
372         /* This object is encodable as a long. Try to use a shared object.
373          * Note that we avoid using shared integers when maxmemory is used
374          * because every object needs to have a private LRU field for the LRU
375          * algorithm to work well. */
376         if ((server.maxmemory == 0 ||
377              (server.maxmemory_policy != REDIS_MAXMEMORY_VOLATILE_LRU &&
378               server.maxmemory_policy != REDIS_MAXMEMORY_ALLKEYS_LRU)) &&
379             value >= 0 &&
380             value < REDIS_SHARED_INTEGERS)
381         {
382             decrRefCount(o);
383             incrRefCount(shared.integers[value]);
384             return shared.integers[value];
385         } else {
386             if (o->encoding == REDIS_ENCODING_RAW) sdsfree(o->ptr);
387             o->encoding = REDIS_ENCODING_INT;
388             o->ptr = (void*) value;
389             return o;
390         }
391     }
392 
393     /* If the string is small and is still RAW encoded,
394      * try the EMBSTR encoding which is more efficient.
395      * In this representation the object and the SDS string are allocated
396      * in the same chunk of memory to save space and cache misses. */
397     if (len <= REDIS_ENCODING_EMBSTR_SIZE_LIMIT) {
398         robj *emb;
399 
400         if (o->encoding == REDIS_ENCODING_EMBSTR) return o;
401         emb = createEmbeddedStringObject(s,sdslen(s));
402         decrRefCount(o);
403         return emb;
404     }
405 
406     /* We can't encode the object...
407      *
408      * Do the last try, and at least optimize the SDS string inside
409      * the string object to require little space, in case there
410      * is more than 10% of free space at the end of the SDS string.
411      *
412      * We do that only for relatively large strings as this branch
413      * is only entered if the length of the string is greater than
414      * REDIS_ENCODING_EMBSTR_SIZE_LIMIT. */
415     if (o->encoding == REDIS_ENCODING_RAW &&
416         sdsavail(s) > len/10)
417     {
418         o->ptr = sdsRemoveFreeSpace(o->ptr);
419     }
420 
421     /* Return the original object. */
422     return o;
423 }
424 
425 /* Get a decoded version of an encoded object (returned as a new object).
426  * If the object is already raw-encoded just increment the ref count. */
427 robj *getDecodedObject(robj *o) {
428     robj *dec;
429 
430     if (sdsEncodedObject(o)) {
431         incrRefCount(o);
432         return o;
433     }
434     if (o->type == REDIS_STRING && o->encoding == REDIS_ENCODING_INT) {
435         char buf[32];
436 
437         ll2string(buf,32,(long)o->ptr);
438         dec = createStringObject(buf,strlen(buf));
439         return dec;
440     } else {
441         redisPanic("Unknown encoding type");
442     }
443 }
444 
445 /* Compare two string objects via strcmp() or strcoll() depending on flags.
446  * Note that the objects may be integer-encoded. In such a case we
447  * use ll2string() to get a string representation of the numbers on the stack
448  * and compare the strings, it's much faster than calling getDecodedObject().
449  *
450  * Important note: when REDIS_COMPARE_BINARY is used a binary-safe comparison
451  * is used. */
452 
453 #define REDIS_COMPARE_BINARY (1<<0)
454 #define REDIS_COMPARE_COLL (1<<1)
455 
456 int compareStringObjectsWithFlags(robj *a, robj *b, int flags) {
457     redisAssertWithInfo(NULL,a,a->type == REDIS_STRING && b->type == REDIS_STRING);
458     char bufa[128], bufb[128], *astr, *bstr;
459     size_t alen, blen, minlen;
460 
461     if (a == b) return 0;
462     if (sdsEncodedObject(a)) {
463         astr = a->ptr;
464         alen = sdslen(astr);
465     } else {
466         alen = ll2string(bufa,sizeof(bufa),(long) a->ptr);
467         astr = bufa;
468     }
469     if (sdsEncodedObject(b)) {
470         bstr = b->ptr;
471         blen = sdslen(bstr);
472     } else {
473         blen = ll2string(bufb,sizeof(bufb),(long) b->ptr);
474         bstr = bufb;
475     }
476     if (flags & REDIS_COMPARE_COLL) {
477         return strcoll(astr,bstr);
478     } else {
479         int cmp;
480 
481         minlen = (alen < blen) ? alen : blen;
482         cmp = memcmp(astr,bstr,minlen);
483         if (cmp == 0) return alen-blen;
484         return cmp;
485     }
486 }
487 
488 /* Wrapper for compareStringObjectsWithFlags() using binary comparison. */
489 int compareStringObjects(robj *a, robj *b) {
490     return compareStringObjectsWithFlags(a,b,REDIS_COMPARE_BINARY);
491 }
492 
493 /* Wrapper for compareStringObjectsWithFlags() using collation. */
494 int collateStringObjects(robj *a, robj *b) {
495     return compareStringObjectsWithFlags(a,b,REDIS_COMPARE_COLL);
496 }
497 
498 /* Equal string objects return 1 if the two objects are the same from the
499  * point of view of a string comparison, otherwise 0 is returned. Note that
500  * this function is faster then checking for (compareStringObject(a,b) == 0)
501  * because it can perform some more optimization. */
502 int equalStringObjects(robj *a, robj *b) {
503     if (a->encoding == REDIS_ENCODING_INT &&
504         b->encoding == REDIS_ENCODING_INT){
505         /* If both strings are integer encoded just check if the stored
506          * long is the same. */
507         return a->ptr == b->ptr;
508     } else {
509         return compareStringObjects(a,b) == 0;
510     }
511 }
512 
513 size_t stringObjectLen(robj *o) {
514     redisAssertWithInfo(NULL,o,o->type == REDIS_STRING);
515     if (sdsEncodedObject(o)) {
516         return sdslen(o->ptr);
517     } else {
518         char buf[32];
519 
520         return ll2string(buf,32,(long)o->ptr);
521     }
522 }
523 
524 int getDoubleFromObject(robj *o, double *target) {
525     double value;
526     char *eptr;
527 
528     if (o == NULL) {
529         value = 0;
530     } else {
531         redisAssertWithInfo(NULL,o,o->type == REDIS_STRING);
532         if (sdsEncodedObject(o)) {
533             errno = 0;
534             value = strtod(o->ptr, &eptr);
535             if (isspace(((char*)o->ptr)[0]) ||
536                 eptr[0] != '\0' ||
537                 (errno == ERANGE &&
538                     (value == HUGE_VAL || value == -HUGE_VAL || value == 0)) ||
539                 errno == EINVAL ||
540                 isnan(value))
541                 return REDIS_ERR;
542         } else if (o->encoding == REDIS_ENCODING_INT) {
543             value = (long)o->ptr;
544         } else {
545             redisPanic("Unknown string encoding");
546         }
547     }
548     *target = value;
549     return REDIS_OK;
550 }
551 
552 int getDoubleFromObjectOrReply(redisClient *c, robj *o, double *target, const char *msg) {
553     double value;
554     if (getDoubleFromObject(o, &value) != REDIS_OK) {
555         if (msg != NULL) {
556             addReplyError(c,(char*)msg);
557         } else {
558             addReplyError(c,"value is not a valid float");
559         }
560         return REDIS_ERR;
561     }
562     *target = value;
563     return REDIS_OK;
564 }
565 
566 int getLongDoubleFromObject(robj *o, long double *target) {
567     long double value;
568     char *eptr;
569 
570     if (o == NULL) {
571         value = 0;
572     } else {
573         redisAssertWithInfo(NULL,o,o->type == REDIS_STRING);
574         if (sdsEncodedObject(o)) {
575             errno = 0;
576             value = strtold(o->ptr, &eptr);
577             if (isspace(((char*)o->ptr)[0]) || eptr[0] != '\0' ||
578                 errno == ERANGE || isnan(value))
579                 return REDIS_ERR;
580         } else if (o->encoding == REDIS_ENCODING_INT) {
581             value = (long)o->ptr;
582         } else {
583             redisPanic("Unknown string encoding");
584         }
585     }
586     *target = value;
587     return REDIS_OK;
588 }
589 
590 int getLongDoubleFromObjectOrReply(redisClient *c, robj *o, long double *target, const char *msg) {
591     long double value;
592     if (getLongDoubleFromObject(o, &value) != REDIS_OK) {
593         if (msg != NULL) {
594             addReplyError(c,(char*)msg);
595         } else {
596             addReplyError(c,"value is not a valid float");
597         }
598         return REDIS_ERR;
599     }
600     *target = value;
601     return REDIS_OK;
602 }
603 
604 int getLongLongFromObject(robj *o, long long *target) {
605     long long value;
606     char *eptr;
607 
608     if (o == NULL) {
609         value = 0;
610     } else {
611         redisAssertWithInfo(NULL,o,o->type == REDIS_STRING);
612         if (sdsEncodedObject(o)) {
613             errno = 0;
614             value = strtoll(o->ptr, &eptr, 10);
615             if (isspace(((char*)o->ptr)[0]) || eptr[0] != '\0' ||
616                 errno == ERANGE)
617                 return REDIS_ERR;
618         } else if (o->encoding == REDIS_ENCODING_INT) {
619             value = (long)o->ptr;
620         } else {
621             redisPanic("Unknown string encoding");
622         }
623     }
624     if (target) *target = value;
625     return REDIS_OK;
626 }
627 
628 int getLongLongFromObjectOrReply(redisClient *c, robj *o, long long *target, const char *msg) {
629     long long value;
630     if (getLongLongFromObject(o, &value) != REDIS_OK) {
631         if (msg != NULL) {
632             addReplyError(c,(char*)msg);
633         } else {
634             addReplyError(c,"value is not an integer or out of range");
635         }
636         return REDIS_ERR;
637     }
638     *target = value;
639     return REDIS_OK;
640 }
641 
642 int getLongFromObjectOrReply(redisClient *c, robj *o, long *target, const char *msg) {
643     long long value;
644 
645     if (getLongLongFromObjectOrReply(c, o, &value, msg) != REDIS_OK) return REDIS_ERR;
646     if (value < LONG_MIN || value > LONG_MAX) {
647         if (msg != NULL) {
648             addReplyError(c,(char*)msg);
649         } else {
650             addReplyError(c,"value is out of range");
651         }
652         return REDIS_ERR;
653     }
654     *target = value;
655     return REDIS_OK;
656 }
657 
658 char *strEncoding(int encoding) {
659     switch(encoding) {
660     case REDIS_ENCODING_RAW: return "raw";
661     case REDIS_ENCODING_INT: return "int";
662     case REDIS_ENCODING_HT: return "hashtable";
663     case REDIS_ENCODING_LINKEDLIST: return "linkedlist";
664     case REDIS_ENCODING_ZIPLIST: return "ziplist";
665     case REDIS_ENCODING_INTSET: return "intset";
666     case REDIS_ENCODING_SKIPLIST: return "skiplist";
667     case REDIS_ENCODING_EMBSTR: return "embstr";
668     default: return "unknown";
669     }
670 }
671 
672 /* Given an object returns the min number of milliseconds the object was never
673  * requested, using an approximated LRU algorithm. */
674 unsigned long long estimateObjectIdleTime(robj *o) {
675     unsigned long long lruclock = LRU_CLOCK();
676     if (lruclock >= o->lru) {
677         return (lruclock - o->lru) * REDIS_LRU_CLOCK_RESOLUTION;
678     } else {
679         return (lruclock + (REDIS_LRU_CLOCK_MAX - o->lru)) *
680                     REDIS_LRU_CLOCK_RESOLUTION;
681     }
682 }
683 
684 /* This is a helper function for the OBJECT command. We need to lookup keys
685  * without any modification of LRU or other parameters. */
686 robj *objectCommandLookup(redisClient *c, robj *key) {
687     dictEntry *de;
688 
689     if ((de = dictFind(c->db->dict,key->ptr)) == NULL) return NULL;
690     return (robj*) dictGetVal(de);
691 }
692 
693 robj *objectCommandLookupOrReply(redisClient *c, robj *key, robj *reply) {
694     robj *o = objectCommandLookup(c,key);
695 
696     if (!o) addReply(c, reply);
697     return o;
698 }
699 
700 /* Object command allows to inspect the internals of an Redis Object.
701  * Usage: OBJECT <refcount|encoding|idletime> <key> */
702 void objectCommand(redisClient *c) {
703     robj *o;
704 
705     if (!strcasecmp(c->argv[1]->ptr,"refcount") && c->argc == 3) {
706         if ((o = objectCommandLookupOrReply(c,c->argv[2],shared.nullbulk))
707                 == NULL) return;
708         addReplyLongLong(c,o->refcount);
709     } else if (!strcasecmp(c->argv[1]->ptr,"encoding") && c->argc == 3) {
710         if ((o = objectCommandLookupOrReply(c,c->argv[2],shared.nullbulk))
711                 == NULL) return;
712         addReplyBulkCString(c,strEncoding(o->encoding));
713     } else if (!strcasecmp(c->argv[1]->ptr,"idletime") && c->argc == 3) {
714         if ((o = objectCommandLookupOrReply(c,c->argv[2],shared.nullbulk))
715                 == NULL) return;
716         addReplyLongLong(c,estimateObjectIdleTime(o)/1000);
717     } else {
718         addReplyError(c,"Syntax error. Try OBJECT (refcount|encoding|idletime)");
719     }
720 }
721 
722