mirror of
https://github.com/fluencelabs/redis
synced 2025-06-13 09:11:20 +00:00
186 lines
5.5 KiB
C
186 lines
5.5 KiB
C
#include "zset.h"
|
|
|
|
/* t_zset.c prototypes (there's no t_zset.h) */
|
|
unsigned char *zzlFirstInRange(unsigned char *zl, zrangespec *range);
|
|
unsigned char *zzlFind(unsigned char *zl, robj *ele, double *score);
|
|
int zzlLexValueLteMax(unsigned char *p, zlexrangespec *spec);
|
|
|
|
/* Converted from static in t_zset.c: */
|
|
int zslValueLteMax(double value, zrangespec *spec);
|
|
|
|
/* ====================================================================
|
|
* Direct Redis DB Interaction
|
|
* ==================================================================== */
|
|
|
|
/* zset access is mostly a copy/paste from zscoreCommand() */
|
|
int zsetScore(robj *zobj, robj *member, double *score) {
|
|
if (!zobj || !member)
|
|
return 0;
|
|
|
|
if (zobj->encoding == REDIS_ENCODING_ZIPLIST) {
|
|
if (zzlFind(zobj->ptr, member, score) == NULL)
|
|
return 0;
|
|
} else if (zobj->encoding == REDIS_ENCODING_SKIPLIST) {
|
|
zset *zs = zobj->ptr;
|
|
dictEntry *de;
|
|
|
|
member = tryObjectEncoding(member);
|
|
de = dictFind(zs->dict, member);
|
|
if (de != NULL) {
|
|
*score = *(double *)dictGetVal(de);
|
|
} else
|
|
return 0;
|
|
} else {
|
|
return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
/* Largely extracted from genericZrangebyscoreCommand() in t_zset.c */
|
|
/* The zrangebyscoreCommand expects to only operate on a live redisClient,
|
|
* but we need results returned to us, not sent over an async socket. */
|
|
list *geozrangebyscore(robj *zobj, double min, double max, int limit) {
|
|
/* minex 0 = include min in range; maxex 1 = exclude max in range */
|
|
/* That's: min <= val < max */
|
|
zrangespec range = { .min = min, .max = max, .minex = 0, .maxex = 1 };
|
|
list *l = NULL; /* result list */
|
|
|
|
if (zobj->encoding == REDIS_ENCODING_ZIPLIST) {
|
|
unsigned char *zl = zobj->ptr;
|
|
unsigned char *eptr, *sptr;
|
|
unsigned char *vstr = NULL;
|
|
unsigned int vlen = 0;
|
|
long long vlong = 0;
|
|
double score = 0;
|
|
|
|
if ((eptr = zzlFirstInRange(zl, &range)) == NULL) {
|
|
/* Nothing exists starting at our min. No results. */
|
|
return NULL;
|
|
}
|
|
|
|
l = listCreate();
|
|
|
|
sptr = ziplistNext(zl, eptr);
|
|
|
|
while (eptr && limit--) {
|
|
score = zzlGetScore(sptr);
|
|
|
|
/* If we fell out of range, break. */
|
|
if (!zslValueLteMax(score, &range))
|
|
break;
|
|
|
|
/* We know the element exists. ziplistGet should always succeed */
|
|
ziplistGet(eptr, &vstr, &vlen, &vlong);
|
|
if (vstr == NULL) {
|
|
listAddNodeTail(l, result_long(score, vlong));
|
|
} else {
|
|
listAddNodeTail(l, result_str(score, vstr, vlen));
|
|
}
|
|
zzlNext(zl, &eptr, &sptr);
|
|
}
|
|
} else if (zobj->encoding == REDIS_ENCODING_SKIPLIST) {
|
|
zset *zs = zobj->ptr;
|
|
zskiplist *zsl = zs->zsl;
|
|
zskiplistNode *ln;
|
|
|
|
if ((ln = zslFirstInRange(zsl, &range)) == NULL) {
|
|
/* Nothing exists starting at our min. No results. */
|
|
return NULL;
|
|
}
|
|
|
|
l = listCreate();
|
|
|
|
while (ln && limit--) {
|
|
robj *o = ln->obj;
|
|
/* Abort when the node is no longer in range. */
|
|
if (!zslValueLteMax(ln->score, &range))
|
|
break;
|
|
|
|
if (o->encoding == REDIS_ENCODING_INT) {
|
|
listAddNodeTail(l, result_long(ln->score, (long)o->ptr));
|
|
} else {
|
|
listAddNodeTail(l,
|
|
result_str(ln->score, o->ptr, sdslen(o->ptr)));
|
|
}
|
|
|
|
ln = ln->level[0].forward;
|
|
}
|
|
}
|
|
if (l) {
|
|
listSetFreeMethod(l, (void (*)(void *ptr)) & free_zipresult);
|
|
}
|
|
|
|
return l;
|
|
}
|
|
|
|
/* ====================================================================
|
|
* Helpers
|
|
* ==================================================================== */
|
|
|
|
/* join 'join' to 'join_to' and free 'join' container */
|
|
void listJoin(list *join_to, list *join) {
|
|
/* If the current list has zero size, move join to become join_to.
|
|
* If not, append the new list to the current list. */
|
|
if (join_to->len == 0) {
|
|
join_to->head = join->head;
|
|
} else {
|
|
join_to->tail->next = join->head;
|
|
join->head->prev = join_to->tail;
|
|
join_to->tail = join->tail;
|
|
}
|
|
|
|
/* Update total element count */
|
|
join_to->len += join->len;
|
|
|
|
/* Release original list container. Internal nodes were transferred over. */
|
|
zfree(join);
|
|
}
|
|
|
|
/* A ziplist member may be either a long long or a string. We create the
|
|
* contents of our return zipresult based on what the ziplist contained. */
|
|
static struct zipresult *result(double score, long long v, unsigned char *s,
|
|
int len) {
|
|
struct zipresult *r = zmalloc(sizeof(*r));
|
|
|
|
/* If string and length, become a string. */
|
|
/* Else, if not string or no length, become a long. */
|
|
if (s && len >= 0)
|
|
r->type = ZR_STRING;
|
|
else if (!s || len < 0)
|
|
r->type = ZR_LONG;
|
|
|
|
r->score = score;
|
|
switch (r->type) {
|
|
case(ZR_LONG) :
|
|
r->val.v = v;
|
|
break;
|
|
case(ZR_STRING) :
|
|
r->val.s = sdsnewlen(s, len);
|
|
break;
|
|
}
|
|
return r;
|
|
}
|
|
|
|
struct zipresult *result_long(double score, long long v) {
|
|
return result(score, v, NULL, -1);
|
|
}
|
|
|
|
struct zipresult *result_str(double score, unsigned char *str, int len) {
|
|
return result(score, 0, str, len);
|
|
}
|
|
|
|
void free_zipresult(struct zipresult *r) {
|
|
if (!r)
|
|
return;
|
|
|
|
switch (r->type) {
|
|
case(ZR_LONG) :
|
|
break;
|
|
case(ZR_STRING) :
|
|
sdsfree(r->val.s);
|
|
break;
|
|
}
|
|
|
|
zfree(r);
|
|
}
|