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