2012-11-08 12:25:23 -05:00
|
|
|
/* Redis Object implementation.
|
|
|
|
*
|
|
|
|
* Copyright (c) 2009-2012, Salvatore Sanfilippo <antirez at gmail dot com>
|
|
|
|
* All rights reserved.
|
|
|
|
*
|
|
|
|
* Redistribution and use in source and binary forms, with or without
|
|
|
|
* modification, are permitted provided that the following conditions are met:
|
|
|
|
*
|
|
|
|
* * Redistributions of source code must retain the above copyright notice,
|
|
|
|
* this list of conditions and the following disclaimer.
|
|
|
|
* * Redistributions in binary form must reproduce the above copyright
|
|
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
|
|
* documentation and/or other materials provided with the distribution.
|
|
|
|
* * Neither the name of Redis nor the names of its contributors may be used
|
|
|
|
* to endorse or promote products derived from this software without
|
|
|
|
* specific prior written permission.
|
|
|
|
*
|
|
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
|
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
|
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
|
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
|
|
|
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
|
|
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
|
|
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
|
|
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
|
|
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
|
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
|
|
* POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
*/
|
|
|
|
|
2010-06-21 18:07:48 -04:00
|
|
|
#include "redis.h"
|
2010-07-29 16:13:31 -04:00
|
|
|
#include <math.h>
|
2011-11-14 09:34:44 -05:00
|
|
|
#include <ctype.h>
|
2010-06-21 18:07:48 -04:00
|
|
|
|
|
|
|
robj *createObject(int type, void *ptr) {
|
2010-11-04 05:09:30 -04:00
|
|
|
robj *o = zmalloc(sizeof(*o));
|
2010-06-21 18:07:48 -04:00
|
|
|
o->type = type;
|
|
|
|
o->encoding = REDIS_ENCODING_RAW;
|
|
|
|
o->ptr = ptr;
|
|
|
|
o->refcount = 1;
|
2010-11-04 05:09:30 -04:00
|
|
|
|
2012-04-02 05:56:03 -04:00
|
|
|
/* Set the LRU to the current lruclock (minutes resolution). */
|
2010-10-14 07:52:58 -04:00
|
|
|
o->lru = server.lruclock;
|
2010-06-21 18:07:48 -04:00
|
|
|
return o;
|
|
|
|
}
|
|
|
|
|
|
|
|
robj *createStringObject(char *ptr, size_t len) {
|
|
|
|
return createObject(REDIS_STRING,sdsnewlen(ptr,len));
|
|
|
|
}
|
|
|
|
|
|
|
|
robj *createStringObjectFromLongLong(long long value) {
|
|
|
|
robj *o;
|
2011-06-25 06:22:03 -04:00
|
|
|
if (value >= 0 && value < REDIS_SHARED_INTEGERS) {
|
2010-06-21 18:07:48 -04:00
|
|
|
incrRefCount(shared.integers[value]);
|
|
|
|
o = shared.integers[value];
|
|
|
|
} else {
|
|
|
|
if (value >= LONG_MIN && value <= LONG_MAX) {
|
|
|
|
o = createObject(REDIS_STRING, NULL);
|
|
|
|
o->encoding = REDIS_ENCODING_INT;
|
|
|
|
o->ptr = (void*)((long)value);
|
|
|
|
} else {
|
|
|
|
o = createObject(REDIS_STRING,sdsfromlonglong(value));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return o;
|
|
|
|
}
|
|
|
|
|
2011-11-12 13:27:35 -05:00
|
|
|
/* Note: this function is defined into object.c since here it is where it
|
|
|
|
* belongs but it is actually designed to be used just for INCRBYFLOAT */
|
|
|
|
robj *createStringObjectFromLongDouble(long double value) {
|
2012-03-22 13:16:41 -04:00
|
|
|
char buf[256];
|
2011-11-12 13:27:35 -05:00
|
|
|
int len;
|
|
|
|
|
|
|
|
/* We use 17 digits precision since with 128 bit floats that precision
|
2013-01-16 12:00:20 -05:00
|
|
|
* after rounding is able to represent most small decimal numbers in a way
|
2011-11-12 13:27:35 -05:00
|
|
|
* that is "non surprising" for the user (that is, most small decimal
|
|
|
|
* numbers will be represented in a way that when converted back into
|
|
|
|
* a string are exactly the same as what the user typed.) */
|
2012-03-22 13:14:32 -04:00
|
|
|
len = snprintf(buf,sizeof(buf),"%.17Lf", value);
|
|
|
|
/* Now remove trailing zeroes after the '.' */
|
2012-03-22 13:16:41 -04:00
|
|
|
if (strchr(buf,'.') != NULL) {
|
|
|
|
char *p = buf+len-1;
|
2012-03-22 13:14:32 -04:00
|
|
|
while(*p == '0') {
|
|
|
|
p--;
|
|
|
|
len--;
|
|
|
|
}
|
|
|
|
if (*p == '.') len--;
|
|
|
|
}
|
2011-11-12 13:27:35 -05:00
|
|
|
return createStringObject(buf,len);
|
|
|
|
}
|
|
|
|
|
2010-06-21 18:07:48 -04:00
|
|
|
robj *dupStringObject(robj *o) {
|
2011-10-04 12:43:03 -04:00
|
|
|
redisAssertWithInfo(NULL,o,o->encoding == REDIS_ENCODING_RAW);
|
2010-06-21 18:07:48 -04:00
|
|
|
return createStringObject(o->ptr,sdslen(o->ptr));
|
|
|
|
}
|
|
|
|
|
|
|
|
robj *createListObject(void) {
|
|
|
|
list *l = listCreate();
|
|
|
|
robj *o = createObject(REDIS_LIST,l);
|
2013-01-25 05:16:00 -05:00
|
|
|
listSetFreeMethod(l,decrRefCountVoid);
|
2010-06-21 18:07:48 -04:00
|
|
|
o->encoding = REDIS_ENCODING_LINKEDLIST;
|
|
|
|
return o;
|
|
|
|
}
|
|
|
|
|
|
|
|
robj *createZiplistObject(void) {
|
|
|
|
unsigned char *zl = ziplistNew();
|
|
|
|
robj *o = createObject(REDIS_LIST,zl);
|
|
|
|
o->encoding = REDIS_ENCODING_ZIPLIST;
|
|
|
|
return o;
|
|
|
|
}
|
|
|
|
|
|
|
|
robj *createSetObject(void) {
|
|
|
|
dict *d = dictCreate(&setDictType,NULL);
|
2010-07-02 13:57:12 -04:00
|
|
|
robj *o = createObject(REDIS_SET,d);
|
|
|
|
o->encoding = REDIS_ENCODING_HT;
|
|
|
|
return o;
|
|
|
|
}
|
|
|
|
|
|
|
|
robj *createIntsetObject(void) {
|
|
|
|
intset *is = intsetNew();
|
|
|
|
robj *o = createObject(REDIS_SET,is);
|
|
|
|
o->encoding = REDIS_ENCODING_INTSET;
|
|
|
|
return o;
|
2010-06-21 18:07:48 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
robj *createHashObject(void) {
|
2012-01-03 01:14:10 -05:00
|
|
|
unsigned char *zl = ziplistNew();
|
|
|
|
robj *o = createObject(REDIS_HASH, zl);
|
|
|
|
o->encoding = REDIS_ENCODING_ZIPLIST;
|
2010-06-21 18:07:48 -04:00
|
|
|
return o;
|
|
|
|
}
|
|
|
|
|
|
|
|
robj *createZsetObject(void) {
|
|
|
|
zset *zs = zmalloc(sizeof(*zs));
|
2011-04-06 09:36:10 -04:00
|
|
|
robj *o;
|
2010-06-21 18:07:48 -04:00
|
|
|
|
|
|
|
zs->dict = dictCreate(&zsetDictType,NULL);
|
|
|
|
zs->zsl = zslCreate();
|
2011-04-06 09:36:10 -04:00
|
|
|
o = createObject(REDIS_ZSET,zs);
|
|
|
|
o->encoding = REDIS_ENCODING_SKIPLIST;
|
|
|
|
return o;
|
2010-06-21 18:07:48 -04:00
|
|
|
}
|
|
|
|
|
2011-03-08 10:08:52 -05:00
|
|
|
robj *createZsetZiplistObject(void) {
|
|
|
|
unsigned char *zl = ziplistNew();
|
|
|
|
robj *o = createObject(REDIS_ZSET,zl);
|
|
|
|
o->encoding = REDIS_ENCODING_ZIPLIST;
|
|
|
|
return o;
|
|
|
|
}
|
|
|
|
|
2010-06-21 18:07:48 -04:00
|
|
|
void freeStringObject(robj *o) {
|
|
|
|
if (o->encoding == REDIS_ENCODING_RAW) {
|
|
|
|
sdsfree(o->ptr);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void freeListObject(robj *o) {
|
|
|
|
switch (o->encoding) {
|
|
|
|
case REDIS_ENCODING_LINKEDLIST:
|
|
|
|
listRelease((list*) o->ptr);
|
|
|
|
break;
|
|
|
|
case REDIS_ENCODING_ZIPLIST:
|
|
|
|
zfree(o->ptr);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
redisPanic("Unknown list encoding type");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void freeSetObject(robj *o) {
|
2010-07-02 13:57:12 -04:00
|
|
|
switch (o->encoding) {
|
|
|
|
case REDIS_ENCODING_HT:
|
|
|
|
dictRelease((dict*) o->ptr);
|
|
|
|
break;
|
|
|
|
case REDIS_ENCODING_INTSET:
|
|
|
|
zfree(o->ptr);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
redisPanic("Unknown set encoding type");
|
|
|
|
}
|
2010-06-21 18:07:48 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
void freeZsetObject(robj *o) {
|
2011-03-08 17:56:59 -05:00
|
|
|
zset *zs;
|
|
|
|
switch (o->encoding) {
|
2011-04-06 10:17:07 -04:00
|
|
|
case REDIS_ENCODING_SKIPLIST:
|
2011-03-08 17:56:59 -05:00
|
|
|
zs = o->ptr;
|
|
|
|
dictRelease(zs->dict);
|
|
|
|
zslFree(zs->zsl);
|
|
|
|
zfree(zs);
|
|
|
|
break;
|
|
|
|
case REDIS_ENCODING_ZIPLIST:
|
|
|
|
zfree(o->ptr);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
redisPanic("Unknown sorted set encoding");
|
|
|
|
}
|
2010-06-21 18:07:48 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
void freeHashObject(robj *o) {
|
|
|
|
switch (o->encoding) {
|
|
|
|
case REDIS_ENCODING_HT:
|
|
|
|
dictRelease((dict*) o->ptr);
|
|
|
|
break;
|
2012-01-03 01:14:10 -05:00
|
|
|
case REDIS_ENCODING_ZIPLIST:
|
2010-06-21 18:07:48 -04:00
|
|
|
zfree(o->ptr);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
redisPanic("Unknown hash encoding type");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void incrRefCount(robj *o) {
|
|
|
|
o->refcount++;
|
|
|
|
}
|
|
|
|
|
2013-01-24 05:27:10 -05:00
|
|
|
void decrRefCount(robj *o) {
|
2010-06-21 18:07:48 -04:00
|
|
|
if (o->refcount <= 0) redisPanic("decrRefCount against refcount <= 0");
|
2011-06-02 11:41:42 -04:00
|
|
|
if (o->refcount == 1) {
|
2010-06-21 18:07:48 -04:00
|
|
|
switch(o->type) {
|
|
|
|
case REDIS_STRING: freeStringObject(o); break;
|
|
|
|
case REDIS_LIST: freeListObject(o); break;
|
|
|
|
case REDIS_SET: freeSetObject(o); break;
|
|
|
|
case REDIS_ZSET: freeZsetObject(o); break;
|
|
|
|
case REDIS_HASH: freeHashObject(o); break;
|
|
|
|
default: redisPanic("Unknown object type"); break;
|
|
|
|
}
|
2010-11-04 05:09:30 -04:00
|
|
|
zfree(o);
|
2011-06-02 11:41:42 -04:00
|
|
|
} else {
|
|
|
|
o->refcount--;
|
2010-06-21 18:07:48 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-01-24 05:27:10 -05:00
|
|
|
/* This variant of decrRefCount() gets its argument as void, and is useful
|
|
|
|
* as free method in data structures that expect a 'void free_object(void*)'
|
|
|
|
* prototype for the free method. */
|
|
|
|
void decrRefCountVoid(void *o) {
|
|
|
|
decrRefCount(o);
|
|
|
|
}
|
|
|
|
|
2011-07-13 09:38:03 -04:00
|
|
|
/* This function set the ref count to zero without freeing the object.
|
|
|
|
* It is useful in order to pass a new object to functions incrementing
|
|
|
|
* the ref count of the received object. Example:
|
|
|
|
*
|
|
|
|
* functionThatWillIncrementRefCount(resetRefCount(CreateObject(...)));
|
|
|
|
*
|
|
|
|
* Otherwise you need to resort to the less elegant pattern:
|
|
|
|
*
|
|
|
|
* *obj = createObject(...);
|
|
|
|
* functionThatWillIncrementRefCount(obj);
|
|
|
|
* decrRefCount(obj);
|
|
|
|
*/
|
|
|
|
robj *resetRefCount(robj *obj) {
|
|
|
|
obj->refcount = 0;
|
|
|
|
return obj;
|
|
|
|
}
|
|
|
|
|
2010-06-21 18:07:48 -04:00
|
|
|
int checkType(redisClient *c, robj *o, int type) {
|
|
|
|
if (o->type != type) {
|
|
|
|
addReply(c,shared.wrongtypeerr);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2011-04-27 07:24:52 -04:00
|
|
|
int isObjectRepresentableAsLongLong(robj *o, long long *llval) {
|
2011-10-04 12:43:03 -04:00
|
|
|
redisAssertWithInfo(NULL,o,o->type == REDIS_STRING);
|
2011-04-27 07:24:52 -04:00
|
|
|
if (o->encoding == REDIS_ENCODING_INT) {
|
|
|
|
if (llval) *llval = (long) o->ptr;
|
|
|
|
return REDIS_OK;
|
|
|
|
} else {
|
|
|
|
return string2ll(o->ptr,sdslen(o->ptr),llval) ? REDIS_OK : REDIS_ERR;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2010-06-21 18:07:48 -04:00
|
|
|
/* Try to encode a string object in order to save space */
|
|
|
|
robj *tryObjectEncoding(robj *o) {
|
|
|
|
long value;
|
|
|
|
sds s = o->ptr;
|
|
|
|
|
|
|
|
if (o->encoding != REDIS_ENCODING_RAW)
|
|
|
|
return o; /* Already encoded */
|
|
|
|
|
|
|
|
/* It's not safe to encode shared objects: shared objects can be shared
|
|
|
|
* everywhere in the "object space" of Redis. Encoded objects can only
|
|
|
|
* appear as "values" (and not, for instance, as keys) */
|
|
|
|
if (o->refcount > 1) return o;
|
|
|
|
|
|
|
|
/* Currently we try to encode only strings */
|
2011-10-04 12:43:03 -04:00
|
|
|
redisAssertWithInfo(NULL,o,o->type == REDIS_STRING);
|
2010-06-21 18:07:48 -04:00
|
|
|
|
|
|
|
/* Check if we can represent this string as a long integer */
|
2011-04-27 07:24:52 -04:00
|
|
|
if (!string2l(s,sdslen(s),&value)) return o;
|
2010-06-21 18:07:48 -04:00
|
|
|
|
2010-07-22 07:08:02 -04:00
|
|
|
/* Ok, this object can be encoded...
|
|
|
|
*
|
2012-03-27 12:46:51 -04:00
|
|
|
* Can I use a shared object? Only if the object is inside a given range
|
2010-10-15 12:04:05 -04:00
|
|
|
*
|
|
|
|
* Note that we also avoid using shared integers when maxmemory is used
|
2010-12-29 13:39:42 -05:00
|
|
|
* because every object needs to have a private LRU field for the LRU
|
2010-10-15 12:04:05 -04:00
|
|
|
* algorithm to work well. */
|
2011-06-25 06:22:03 -04:00
|
|
|
if (server.maxmemory == 0 && value >= 0 && value < REDIS_SHARED_INTEGERS) {
|
2010-06-21 18:07:48 -04:00
|
|
|
decrRefCount(o);
|
|
|
|
incrRefCount(shared.integers[value]);
|
|
|
|
return shared.integers[value];
|
|
|
|
} else {
|
|
|
|
o->encoding = REDIS_ENCODING_INT;
|
|
|
|
sdsfree(o->ptr);
|
|
|
|
o->ptr = (void*) value;
|
|
|
|
return o;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Get a decoded version of an encoded object (returned as a new object).
|
|
|
|
* If the object is already raw-encoded just increment the ref count. */
|
|
|
|
robj *getDecodedObject(robj *o) {
|
|
|
|
robj *dec;
|
|
|
|
|
|
|
|
if (o->encoding == REDIS_ENCODING_RAW) {
|
|
|
|
incrRefCount(o);
|
|
|
|
return o;
|
|
|
|
}
|
|
|
|
if (o->type == REDIS_STRING && o->encoding == REDIS_ENCODING_INT) {
|
|
|
|
char buf[32];
|
|
|
|
|
|
|
|
ll2string(buf,32,(long)o->ptr);
|
|
|
|
dec = createStringObject(buf,strlen(buf));
|
|
|
|
return dec;
|
|
|
|
} else {
|
|
|
|
redisPanic("Unknown encoding type");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Compare two string objects via strcmp() or alike.
|
|
|
|
* Note that the objects may be integer-encoded. In such a case we
|
|
|
|
* use ll2string() to get a string representation of the numbers on the stack
|
|
|
|
* and compare the strings, it's much faster than calling getDecodedObject().
|
|
|
|
*
|
|
|
|
* Important note: if objects are not integer encoded, but binary-safe strings,
|
|
|
|
* sdscmp() from sds.c will apply memcmp() so this function ca be considered
|
|
|
|
* binary safe. */
|
|
|
|
int compareStringObjects(robj *a, robj *b) {
|
2011-10-04 12:43:03 -04:00
|
|
|
redisAssertWithInfo(NULL,a,a->type == REDIS_STRING && b->type == REDIS_STRING);
|
2010-06-21 18:07:48 -04:00
|
|
|
char bufa[128], bufb[128], *astr, *bstr;
|
|
|
|
int bothsds = 1;
|
|
|
|
|
|
|
|
if (a == b) return 0;
|
|
|
|
if (a->encoding != REDIS_ENCODING_RAW) {
|
|
|
|
ll2string(bufa,sizeof(bufa),(long) a->ptr);
|
|
|
|
astr = bufa;
|
|
|
|
bothsds = 0;
|
|
|
|
} else {
|
|
|
|
astr = a->ptr;
|
|
|
|
}
|
|
|
|
if (b->encoding != REDIS_ENCODING_RAW) {
|
|
|
|
ll2string(bufb,sizeof(bufb),(long) b->ptr);
|
|
|
|
bstr = bufb;
|
|
|
|
bothsds = 0;
|
|
|
|
} else {
|
|
|
|
bstr = b->ptr;
|
|
|
|
}
|
|
|
|
return bothsds ? sdscmp(astr,bstr) : strcmp(astr,bstr);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Equal string objects return 1 if the two objects are the same from the
|
|
|
|
* point of view of a string comparison, otherwise 0 is returned. Note that
|
|
|
|
* this function is faster then checking for (compareStringObject(a,b) == 0)
|
|
|
|
* because it can perform some more optimization. */
|
|
|
|
int equalStringObjects(robj *a, robj *b) {
|
|
|
|
if (a->encoding != REDIS_ENCODING_RAW && b->encoding != REDIS_ENCODING_RAW){
|
|
|
|
return a->ptr == b->ptr;
|
|
|
|
} else {
|
|
|
|
return compareStringObjects(a,b) == 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
size_t stringObjectLen(robj *o) {
|
2011-10-04 12:43:03 -04:00
|
|
|
redisAssertWithInfo(NULL,o,o->type == REDIS_STRING);
|
2010-06-21 18:07:48 -04:00
|
|
|
if (o->encoding == REDIS_ENCODING_RAW) {
|
|
|
|
return sdslen(o->ptr);
|
|
|
|
} else {
|
|
|
|
char buf[32];
|
|
|
|
|
|
|
|
return ll2string(buf,32,(long)o->ptr);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
int getDoubleFromObject(robj *o, double *target) {
|
|
|
|
double value;
|
|
|
|
char *eptr;
|
|
|
|
|
|
|
|
if (o == NULL) {
|
|
|
|
value = 0;
|
|
|
|
} else {
|
2011-10-04 12:43:03 -04:00
|
|
|
redisAssertWithInfo(NULL,o,o->type == REDIS_STRING);
|
2010-06-21 18:07:48 -04:00
|
|
|
if (o->encoding == REDIS_ENCODING_RAW) {
|
2011-11-12 13:27:35 -05:00
|
|
|
errno = 0;
|
2010-06-21 18:07:48 -04:00
|
|
|
value = strtod(o->ptr, &eptr);
|
2011-11-14 09:34:44 -05:00
|
|
|
if (isspace(((char*)o->ptr)[0]) || eptr[0] != '\0' ||
|
|
|
|
errno == ERANGE || isnan(value))
|
2011-11-12 13:27:35 -05:00
|
|
|
return REDIS_ERR;
|
2010-06-21 18:07:48 -04:00
|
|
|
} else if (o->encoding == REDIS_ENCODING_INT) {
|
|
|
|
value = (long)o->ptr;
|
|
|
|
} else {
|
|
|
|
redisPanic("Unknown string encoding");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
*target = value;
|
|
|
|
return REDIS_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
int getDoubleFromObjectOrReply(redisClient *c, robj *o, double *target, const char *msg) {
|
|
|
|
double value;
|
|
|
|
if (getDoubleFromObject(o, &value) != REDIS_OK) {
|
|
|
|
if (msg != NULL) {
|
2010-09-02 13:52:24 -04:00
|
|
|
addReplyError(c,(char*)msg);
|
2010-06-21 18:07:48 -04:00
|
|
|
} else {
|
2011-11-12 13:27:35 -05:00
|
|
|
addReplyError(c,"value is not a valid float");
|
|
|
|
}
|
|
|
|
return REDIS_ERR;
|
|
|
|
}
|
|
|
|
*target = value;
|
|
|
|
return REDIS_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
int getLongDoubleFromObject(robj *o, long double *target) {
|
|
|
|
long double value;
|
|
|
|
char *eptr;
|
|
|
|
|
|
|
|
if (o == NULL) {
|
|
|
|
value = 0;
|
|
|
|
} else {
|
|
|
|
redisAssertWithInfo(NULL,o,o->type == REDIS_STRING);
|
|
|
|
if (o->encoding == REDIS_ENCODING_RAW) {
|
|
|
|
errno = 0;
|
|
|
|
value = strtold(o->ptr, &eptr);
|
2011-11-14 09:34:44 -05:00
|
|
|
if (isspace(((char*)o->ptr)[0]) || eptr[0] != '\0' ||
|
|
|
|
errno == ERANGE || isnan(value))
|
2011-11-12 13:27:35 -05:00
|
|
|
return REDIS_ERR;
|
|
|
|
} else if (o->encoding == REDIS_ENCODING_INT) {
|
|
|
|
value = (long)o->ptr;
|
|
|
|
} else {
|
|
|
|
redisPanic("Unknown string encoding");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
*target = value;
|
|
|
|
return REDIS_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
int getLongDoubleFromObjectOrReply(redisClient *c, robj *o, long double *target, const char *msg) {
|
|
|
|
long double value;
|
|
|
|
if (getLongDoubleFromObject(o, &value) != REDIS_OK) {
|
|
|
|
if (msg != NULL) {
|
|
|
|
addReplyError(c,(char*)msg);
|
|
|
|
} else {
|
|
|
|
addReplyError(c,"value is not a valid float");
|
2010-06-21 18:07:48 -04:00
|
|
|
}
|
|
|
|
return REDIS_ERR;
|
|
|
|
}
|
|
|
|
*target = value;
|
|
|
|
return REDIS_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
int getLongLongFromObject(robj *o, long long *target) {
|
|
|
|
long long value;
|
|
|
|
char *eptr;
|
|
|
|
|
|
|
|
if (o == NULL) {
|
|
|
|
value = 0;
|
|
|
|
} else {
|
2011-10-04 12:43:03 -04:00
|
|
|
redisAssertWithInfo(NULL,o,o->type == REDIS_STRING);
|
2010-06-21 18:07:48 -04:00
|
|
|
if (o->encoding == REDIS_ENCODING_RAW) {
|
2011-11-14 09:34:44 -05:00
|
|
|
errno = 0;
|
2010-06-21 18:07:48 -04:00
|
|
|
value = strtoll(o->ptr, &eptr, 10);
|
2011-11-14 09:34:44 -05:00
|
|
|
if (isspace(((char*)o->ptr)[0]) || eptr[0] != '\0' ||
|
|
|
|
errno == ERANGE)
|
2010-08-23 11:06:38 -04:00
|
|
|
return REDIS_ERR;
|
2010-06-21 18:07:48 -04:00
|
|
|
} else if (o->encoding == REDIS_ENCODING_INT) {
|
|
|
|
value = (long)o->ptr;
|
|
|
|
} else {
|
|
|
|
redisPanic("Unknown string encoding");
|
|
|
|
}
|
|
|
|
}
|
2010-07-02 13:57:12 -04:00
|
|
|
if (target) *target = value;
|
2010-06-21 18:07:48 -04:00
|
|
|
return REDIS_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
int getLongLongFromObjectOrReply(redisClient *c, robj *o, long long *target, const char *msg) {
|
|
|
|
long long value;
|
|
|
|
if (getLongLongFromObject(o, &value) != REDIS_OK) {
|
|
|
|
if (msg != NULL) {
|
2010-09-02 13:52:24 -04:00
|
|
|
addReplyError(c,(char*)msg);
|
2010-06-21 18:07:48 -04:00
|
|
|
} else {
|
2010-09-02 13:52:24 -04:00
|
|
|
addReplyError(c,"value is not an integer or out of range");
|
2010-06-21 18:07:48 -04:00
|
|
|
}
|
|
|
|
return REDIS_ERR;
|
|
|
|
}
|
|
|
|
*target = value;
|
|
|
|
return REDIS_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
int getLongFromObjectOrReply(redisClient *c, robj *o, long *target, const char *msg) {
|
|
|
|
long long value;
|
|
|
|
|
|
|
|
if (getLongLongFromObjectOrReply(c, o, &value, msg) != REDIS_OK) return REDIS_ERR;
|
|
|
|
if (value < LONG_MIN || value > LONG_MAX) {
|
|
|
|
if (msg != NULL) {
|
2010-09-02 13:52:24 -04:00
|
|
|
addReplyError(c,(char*)msg);
|
2010-06-21 18:07:48 -04:00
|
|
|
} else {
|
2010-09-02 13:52:24 -04:00
|
|
|
addReplyError(c,"value is out of range");
|
2010-06-21 18:07:48 -04:00
|
|
|
}
|
|
|
|
return REDIS_ERR;
|
|
|
|
}
|
|
|
|
*target = value;
|
|
|
|
return REDIS_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
char *strEncoding(int encoding) {
|
|
|
|
switch(encoding) {
|
|
|
|
case REDIS_ENCODING_RAW: return "raw";
|
|
|
|
case REDIS_ENCODING_INT: return "int";
|
|
|
|
case REDIS_ENCODING_HT: return "hashtable";
|
|
|
|
case REDIS_ENCODING_LINKEDLIST: return "linkedlist";
|
|
|
|
case REDIS_ENCODING_ZIPLIST: return "ziplist";
|
2010-07-02 13:57:12 -04:00
|
|
|
case REDIS_ENCODING_INTSET: return "intset";
|
2011-04-06 09:36:10 -04:00
|
|
|
case REDIS_ENCODING_SKIPLIST: return "skiplist";
|
2010-06-21 18:07:48 -04:00
|
|
|
default: return "unknown";
|
|
|
|
}
|
|
|
|
}
|
2010-10-14 07:52:58 -04:00
|
|
|
|
|
|
|
/* Given an object returns the min number of seconds the object was never
|
|
|
|
* requested, using an approximated LRU algorithm. */
|
|
|
|
unsigned long estimateObjectIdleTime(robj *o) {
|
|
|
|
if (server.lruclock >= o->lru) {
|
2010-10-14 15:22:21 -04:00
|
|
|
return (server.lruclock - o->lru) * REDIS_LRU_CLOCK_RESOLUTION;
|
2010-10-14 07:52:58 -04:00
|
|
|
} else {
|
2010-10-14 15:22:21 -04:00
|
|
|
return ((REDIS_LRU_CLOCK_MAX - o->lru) + server.lruclock) *
|
|
|
|
REDIS_LRU_CLOCK_RESOLUTION;
|
2010-10-14 07:52:58 -04:00
|
|
|
}
|
|
|
|
}
|
2011-04-06 06:19:45 -04:00
|
|
|
|
|
|
|
/* This is an helper function for the DEBUG command. We need to lookup keys
|
|
|
|
* without any modification of LRU or other parameters. */
|
|
|
|
robj *objectCommandLookup(redisClient *c, robj *key) {
|
|
|
|
dictEntry *de;
|
|
|
|
|
|
|
|
if ((de = dictFind(c->db->dict,key->ptr)) == NULL) return NULL;
|
2011-11-08 11:07:55 -05:00
|
|
|
return (robj*) dictGetVal(de);
|
2011-04-06 06:19:45 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
robj *objectCommandLookupOrReply(redisClient *c, robj *key, robj *reply) {
|
|
|
|
robj *o = objectCommandLookup(c,key);
|
|
|
|
|
|
|
|
if (!o) addReply(c, reply);
|
|
|
|
return o;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Object command allows to inspect the internals of an Redis Object.
|
|
|
|
* Usage: OBJECT <verb> ... arguments ... */
|
|
|
|
void objectCommand(redisClient *c) {
|
|
|
|
robj *o;
|
|
|
|
|
|
|
|
if (!strcasecmp(c->argv[1]->ptr,"refcount") && c->argc == 3) {
|
|
|
|
if ((o = objectCommandLookupOrReply(c,c->argv[2],shared.nullbulk))
|
|
|
|
== NULL) return;
|
|
|
|
addReplyLongLong(c,o->refcount);
|
|
|
|
} else if (!strcasecmp(c->argv[1]->ptr,"encoding") && c->argc == 3) {
|
|
|
|
if ((o = objectCommandLookupOrReply(c,c->argv[2],shared.nullbulk))
|
|
|
|
== NULL) return;
|
|
|
|
addReplyBulkCString(c,strEncoding(o->encoding));
|
|
|
|
} else if (!strcasecmp(c->argv[1]->ptr,"idletime") && c->argc == 3) {
|
|
|
|
if ((o = objectCommandLookupOrReply(c,c->argv[2],shared.nullbulk))
|
|
|
|
== NULL) return;
|
|
|
|
addReplyLongLong(c,estimateObjectIdleTime(o));
|
|
|
|
} else {
|
|
|
|
addReplyError(c,"Syntax error. Try OBJECT (refcount|encoding|idletime)");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|