summaryrefslogtreecommitdiff
path: root/src/zset.c
blob: 1f69d3f0279eb80cd1bab06e8c9dafce7776ae0f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
#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);
}