Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
Menu
Open sidebar
ruanhaishen
redis
Commits
21c5b508
Commit
21c5b508
authored
Mar 08, 2011
by
Pieter Noordhuis
Browse files
Initial work for ziplist backed sorted sets
parent
9e7cee0e
Changes
1
Show whitespace changes
Inline
Side-by-side
src/t_zset.c
View file @
21c5b508
...
...
@@ -403,6 +403,157 @@ static int zslParseRange(robj *min, robj *max, zrangespec *spec) {
return
REDIS_OK
;
}
/*-----------------------------------------------------------------------------
* Ziplist-backed sorted set API
*----------------------------------------------------------------------------*/
double
zzlGetScore
(
unsigned
char
*
sptr
)
{
unsigned
char
*
vstr
;
unsigned
int
vlen
;
long
long
vlong
;
char
buf
[
128
];
double
score
;
redisAssert
(
sptr
!=
NULL
);
redisAssert
(
ziplistGet
(
sptr
,
&
vstr
,
&
vlen
,
&
vlong
));
if
(
vstr
)
{
memcpy
(
buf
,
vstr
,
vlen
);
buf
[
vlen
]
=
'\0'
;
score
=
strtod
(
buf
,
NULL
);
}
else
{
score
=
vlong
;
}
return
score
;
}
/* Compare element in sorted set with given element. */
int
zzlCompareElements
(
unsigned
char
*
eptr
,
unsigned
char
*
cstr
,
unsigned
int
clen
)
{
unsigned
char
*
vstr
;
unsigned
int
vlen
;
long
long
vlong
;
unsigned
char
vbuf
[
32
];
int
minlen
,
cmp
;
redisAssert
(
ziplistGet
(
eptr
,
&
vstr
,
&
vlen
,
&
vlong
));
if
(
vstr
==
NULL
)
{
/* Store string representation of long long in buf. */
vlen
=
ll2string
((
char
*
)
vbuf
,
sizeof
(
vbuf
),
vlong
);
vstr
=
vbuf
;
}
minlen
=
(
vlen
<
clen
)
?
vlen
:
clen
;
cmp
=
memcmp
(
vstr
,
cstr
,
minlen
);
if
(
cmp
==
0
)
return
vlen
-
clen
;
return
cmp
;
}
unsigned
char
*
zzlFind
(
robj
*
zobj
,
robj
*
ele
,
double
*
score
)
{
unsigned
char
*
zl
=
zobj
->
ptr
;
unsigned
char
*
eptr
=
ziplistIndex
(
zl
,
0
),
*
sptr
;
ele
=
getDecodedObject
(
ele
);
while
(
eptr
!=
NULL
)
{
sptr
=
ziplistNext
(
zl
,
eptr
);
redisAssert
(
sptr
!=
NULL
);
if
(
ziplistCompare
(
eptr
,
ele
->
ptr
,
sdslen
(
ele
->
ptr
)))
{
/* Matching element, pull out score. */
*
score
=
zzlGetScore
(
sptr
);
decrRefCount
(
ele
);
return
eptr
;
}
/* Move to next element. */
eptr
=
ziplistNext
(
zl
,
sptr
);
}
decrRefCount
(
ele
);
return
NULL
;
}
/* Delete (element,score) pair from ziplist. Use local copy of eptr because we
* don't want to modify the one given as argument. */
int
zzlDelete
(
robj
*
zobj
,
unsigned
char
*
eptr
)
{
unsigned
char
*
zl
=
zobj
->
ptr
;
unsigned
char
*
p
=
eptr
;
/* TODO: add function to ziplist API to delete N elements from offset. */
zl
=
ziplistDelete
(
zl
,
&
p
);
zl
=
ziplistDelete
(
zl
,
&
p
);
zobj
->
ptr
=
zl
;
return
REDIS_OK
;
}
int
zzlInsertAt
(
robj
*
zobj
,
robj
*
ele
,
double
score
,
unsigned
char
*
eptr
)
{
unsigned
char
*
zl
=
zobj
->
ptr
;
unsigned
char
*
sptr
;
char
scorebuf
[
128
];
int
scorelen
;
int
offset
;
redisAssert
(
ele
->
encoding
==
REDIS_ENCODING_RAW
);
scorelen
=
d2string
(
scorebuf
,
sizeof
(
scorebuf
),
score
);
if
(
eptr
==
NULL
)
{
zl
=
ziplistPush
(
zl
,
ele
->
ptr
,
sdslen
(
ele
->
ptr
),
ZIPLIST_TAIL
);
zl
=
ziplistPush
(
zl
,(
unsigned
char
*
)
scorebuf
,
scorelen
,
ZIPLIST_TAIL
);
}
else
{
/* Keep offset relative to zl, as it might be re-allocated. */
offset
=
eptr
-
zl
;
zl
=
ziplistInsert
(
zl
,
eptr
,
ele
->
ptr
,
sdslen
(
ele
->
ptr
));
eptr
=
zl
+
offset
;
/* Insert score after the element. */
redisAssert
((
sptr
=
ziplistNext
(
zl
,
eptr
))
!=
NULL
);
zl
=
ziplistInsert
(
zl
,
sptr
,(
unsigned
char
*
)
scorebuf
,
scorelen
);
}
zobj
->
ptr
=
zl
;
return
REDIS_OK
;
}
/* Insert (element,score) pair in ziplist. This function assumes the element is
* not yet present in the list. */
int
zzlInsert
(
robj
*
zobj
,
robj
*
ele
,
double
score
)
{
unsigned
char
*
zl
=
zobj
->
ptr
;
unsigned
char
*
eptr
=
ziplistIndex
(
zl
,
0
),
*
sptr
;
double
s
;
int
insert
=
0
;
ele
=
getDecodedObject
(
ele
);
while
(
eptr
!=
NULL
)
{
sptr
=
ziplistNext
(
zl
,
eptr
);
redisAssert
(
sptr
!=
NULL
);
s
=
zzlGetScore
(
sptr
);
if
(
s
>
score
)
{
/* First element with score larger than score for element to be
* inserted. This means we should take its spot in the list to
* maintain ordering. */
insert
=
1
;
}
else
if
(
s
==
score
)
{
/* Ensure lexicographical ordering for elements. */
if
(
zzlCompareElements
(
eptr
,
ele
->
ptr
,
sdslen
(
ele
->
ptr
))
<
0
)
insert
=
1
;
}
if
(
insert
)
{
zzlInsertAt
(
zobj
,
ele
,
score
,
eptr
);
break
;
}
/* Move to next element. */
eptr
=
ziplistNext
(
zl
,
sptr
);
}
/* Push on tail of list when it was not yet inserted. */
if
(
!
insert
)
zzlInsertAt
(
zobj
,
ele
,
score
,
eptr
);
decrRefCount
(
ele
);
return
REDIS_OK
;
}
/*-----------------------------------------------------------------------------
* Sorted set commands
...
...
@@ -410,90 +561,113 @@ static int zslParseRange(robj *min, robj *max, zrangespec *spec) {
/* This generic command implements both ZADD and ZINCRBY. */
void
zaddGenericCommand
(
redisClient
*
c
,
robj
*
key
,
robj
*
ele
,
double
score
,
int
incr
)
{
robj
*
zsetobj
;
zset
*
zs
;
zskiplistNode
*
znode
;
static
char
*
nanerr
=
"resulting score is not a number (NaN)"
;
robj
*
zobj
;
robj
*
curobj
;
double
curscore
=
0
.
0
;
z
set
obj
=
lookupKeyWrite
(
c
->
db
,
key
);
if
(
z
set
obj
==
NULL
)
{
z
set
obj
=
createZsetObject
();
dbAdd
(
c
->
db
,
key
,
z
set
obj
);
zobj
=
lookupKeyWrite
(
c
->
db
,
key
);
if
(
zobj
==
NULL
)
{
zobj
=
createZset
Ziplist
Object
();
dbAdd
(
c
->
db
,
key
,
zobj
);
}
else
{
if
(
z
set
obj
->
type
!=
REDIS_ZSET
)
{
if
(
zobj
->
type
!=
REDIS_ZSET
)
{
addReply
(
c
,
shared
.
wrongtypeerr
);
return
;
}
}
zs
=
zsetobj
->
ptr
;
/* Since both ZADD and ZINCRBY are implemented here, we need to increment
* the score first by the current score if ZINCRBY is called. */
if
(
incr
)
{
/* Read the old score. If the element was not present starts from 0 */
dictEntry
*
de
=
dictFind
(
zs
->
dict
,
ele
);
if
(
de
!=
NULL
)
score
+=
*
(
double
*
)
dictGetEntryVal
(
de
);
if
(
zobj
->
encoding
==
REDIS_ENCODING_ZIPLIST
)
{
unsigned
char
*
eptr
;
if
((
eptr
=
zzlFind
(
zobj
,
ele
,
&
curscore
))
!=
NULL
)
{
if
(
incr
)
{
score
+=
curscore
;
if
(
isnan
(
score
))
{
addReplyError
(
c
,
"resulting score is not a number (NaN)"
);
/* Note that we don't need to check if the zset may be empty and
* should be removed here, as we can only obtain Nan as score if
* there was already an element in the sorted set. */
addReplyError
(
c
,
nanerr
);
/* Don't need to check if the sorted set is empty, because
* we know it has at least one element. */
return
;
}
}
/* We need to remove and re-insert the element when it was already present
* in the dictionary, to update the skiplist. Note that we delay adding a
* pointer to the score because we want to reference the score in the
* skiplist node. */
if
(
dictAdd
(
zs
->
dict
,
ele
,
NULL
)
==
DICT_OK
)
{
dictEntry
*
de
;
/* Remove and re-insert when score changed. */
if
(
score
!=
curscore
)
{
redisAssert
(
zzlDelete
(
zobj
,
eptr
)
==
REDIS_OK
);
redisAssert
(
zzlInsert
(
zobj
,
ele
,
score
)
==
REDIS_OK
);
/* New element */
incrRefCount
(
ele
);
/* added to hash */
znode
=
zslInsert
(
zs
->
zsl
,
score
,
ele
);
incrRefCount
(
ele
);
/* added to skiplist */
signalModifiedKey
(
c
->
db
,
key
);
server
.
dirty
++
;
}
/* Update the score in the dict entry */
de
=
dictFind
(
zs
->
dict
,
ele
);
redisAssert
(
de
!=
NULL
);
dictGetEntryVal
(
de
)
=
&
znode
->
score
;
signalModifiedKey
(
c
->
db
,
c
->
argv
[
1
]);
if
(
incr
)
/* ZINCRBY */
addReplyDouble
(
c
,
score
);
else
/* ZADD */
addReply
(
c
,
shared
.
czero
);
}
else
{
redisAssert
(
zzlInsert
(
zobj
,
ele
,
score
)
==
REDIS_OK
);
signalModifiedKey
(
c
->
db
,
key
);
server
.
dirty
++
;
if
(
incr
)
if
(
incr
)
/* ZINCRBY */
addReplyDouble
(
c
,
score
);
else
else
/* ZADD */
addReply
(
c
,
shared
.
cone
);
}
else
{
}
}
else
if
(
zobj
->
encoding
==
REDIS_ENCODING_RAW
)
{
zset
*
zs
=
zobj
->
ptr
;
zskiplistNode
*
znode
;
dictEntry
*
de
;
robj
*
curobj
;
double
*
curscore
;
int
deleted
;
/* Update score */
de
=
dictFind
(
zs
->
dict
,
ele
);
redisAssert
(
de
!=
NULL
)
;
if
(
de
!=
NULL
)
{
curobj
=
dictGetEntryKey
(
de
);
curscore
=
dictGetEntryVal
(
de
);
curscore
=
*
(
double
*
)
dictGetEntryVal
(
de
);
/* When the score is updated, reuse the existing string object to
* prevent extra alloc/dealloc of strings on ZINCRBY. */
if
(
score
!=
*
curscore
)
{
deleted
=
zslDelete
(
zs
->
zsl
,
*
curscore
,
curobj
);
redisAssert
(
deleted
!=
0
);
if
(
incr
)
{
score
+=
curscore
;
if
(
isnan
(
score
))
{
addReplyError
(
c
,
nanerr
);
/* Don't need to check if the sorted set is empty, because
* we know it has at least one element. */
return
;
}
}
/* Remove and re-insert when score changed. We can safely delete
* the key object from the skiplist, since the dictionary still has
* a reference to it. */
if
(
score
!=
curscore
)
{
redisAssert
(
zslDelete
(
zs
->
zsl
,
curscore
,
curobj
));
znode
=
zslInsert
(
zs
->
zsl
,
score
,
curobj
);
incrRefCount
(
curobj
);
incrRefCount
(
curobj
);
/* Re-inserted in skiplist. */
dictGetEntryVal
(
de
)
=
&
znode
->
score
;
/* Update score ptr. */
/* Update the score in the current dict entry */
dictGetEntryVal
(
de
)
=
&
znode
->
score
;
signalModifiedKey
(
c
->
db
,
c
->
argv
[
1
]);
signalModifiedKey
(
c
->
db
,
key
);
server
.
dirty
++
;
}
if
(
incr
)
if
(
incr
)
/* ZINCRBY */
addReplyDouble
(
c
,
score
);
else
else
/* ZADD */
addReply
(
c
,
shared
.
czero
);
}
else
{
znode
=
zslInsert
(
zs
->
zsl
,
score
,
ele
);
incrRefCount
(
ele
);
/* Inserted in skiplist. */
redisAssert
(
dictAdd
(
zs
->
dict
,
ele
,
&
znode
->
score
)
==
DICT_OK
);
incrRefCount
(
ele
);
/* Added to dictionary. */
signalModifiedKey
(
c
->
db
,
key
);
server
.
dirty
++
;
if
(
incr
)
/* ZINCRBY */
addReplyDouble
(
c
,
score
);
else
/* ZADD */
addReply
(
c
,
shared
.
cone
);
}
}
else
{
redisPanic
(
"Unknown sorted set encoding"
);
}
}
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment