#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); }