Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Menu
Open sidebar
ruanhaishen
redis
Commits
b04ce2a3
Commit
b04ce2a3
authored
Oct 13, 2010
by
Pieter Noordhuis
Browse files
Merge master with resolved conflict in src/redis-cli.c
parents
4fe83b55
b4f2e412
Changes
72
Show whitespace changes
Inline
Side-by-side
src/sds.c
View file @
b04ce2a3
...
...
@@ -33,7 +33,6 @@
#include "sds.h"
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <ctype.h>
#include "zmalloc.h"
...
...
@@ -156,8 +155,8 @@ sds sdscpy(sds s, char *t) {
return
sdscpylen
(
s
,
t
,
strlen
(
t
));
}
sds
sdscatprintf
(
sds
s
,
const
char
*
fmt
,
...
)
{
va_list
ap
;
sds
sdscat
v
printf
(
sds
s
,
const
char
*
fmt
,
va_list
ap
)
{
va_list
cpy
;
char
*
buf
,
*
t
;
size_t
buflen
=
16
;
...
...
@@ -169,9 +168,8 @@ sds sdscatprintf(sds s, const char *fmt, ...) {
if
(
buf
==
NULL
)
return
NULL
;
#endif
buf
[
buflen
-
2
]
=
'\0'
;
va_start
(
ap
,
fmt
);
vsnprintf
(
buf
,
buflen
,
fmt
,
ap
);
va_end
(
ap
);
va_copy
(
cpy
,
ap
);
vsnprintf
(
buf
,
buflen
,
fmt
,
cpy
);
if
(
buf
[
buflen
-
2
]
!=
'\0'
)
{
zfree
(
buf
);
buflen
*=
2
;
...
...
@@ -184,6 +182,15 @@ sds sdscatprintf(sds s, const char *fmt, ...) {
return
t
;
}
sds
sdscatprintf
(
sds
s
,
const
char
*
fmt
,
...)
{
va_list
ap
;
char
*
t
;
va_start
(
ap
,
fmt
);
t
=
sdscatvprintf
(
s
,
fmt
,
ap
);
va_end
(
ap
);
return
t
;
}
sds
sdstrim
(
sds
s
,
const
char
*
cset
)
{
struct
sdshdr
*
sh
=
(
void
*
)
(
s
-
(
sizeof
(
struct
sdshdr
)));
char
*
start
,
*
end
,
*
sp
,
*
ep
;
...
...
@@ -216,13 +223,16 @@ sds sdsrange(sds s, int start, int end) {
}
newlen
=
(
start
>
end
)
?
0
:
(
end
-
start
)
+
1
;
if
(
newlen
!=
0
)
{
if
(
start
>=
(
signed
)
len
)
start
=
len
-
1
;
if
(
end
>=
(
signed
)
len
)
end
=
len
-
1
;
if
(
start
>=
(
signed
)
len
)
{
newlen
=
0
;
}
else
if
(
end
>=
(
signed
)
len
)
{
end
=
len
-
1
;
newlen
=
(
start
>
end
)
?
0
:
(
end
-
start
)
+
1
;
}
}
else
{
start
=
0
;
}
if
(
start
!=
0
)
memmove
(
sh
->
buf
,
sh
->
buf
+
start
,
newlen
);
if
(
start
&&
newlen
)
memmove
(
sh
->
buf
,
sh
->
buf
+
start
,
newlen
);
sh
->
buf
[
newlen
]
=
0
;
sh
->
free
=
sh
->
free
+
(
sh
->
len
-
newlen
);
sh
->
len
=
newlen
;
...
...
@@ -382,3 +392,182 @@ sds sdscatrepr(sds s, char *p, size_t len) {
}
return
sdscatlen
(
s
,
"
\"
"
,
1
);
}
/* Split a line into arguments, where every argument can be in the
* following programming-language REPL-alike form:
*
* foo bar "newline are supported\n" and "\xff\x00otherstuff"
*
* The number of arguments is stored into *argc, and an array
* of sds is returned. The caller should sdsfree() all the returned
* strings and finally zfree() the array itself.
*
* Note that sdscatrepr() is able to convert back a string into
* a quoted string in the same format sdssplitargs() is able to parse.
*/
sds
*
sdssplitargs
(
char
*
line
,
int
*
argc
)
{
char
*
p
=
line
;
char
*
current
=
NULL
;
char
**
vector
=
NULL
;
*
argc
=
0
;
while
(
1
)
{
/* skip blanks */
while
(
*
p
&&
isspace
(
*
p
))
p
++
;
if
(
*
p
)
{
/* get a token */
int
inq
=
0
;
/* set to 1 if we are in "quotes" */
int
done
=
0
;
if
(
current
==
NULL
)
current
=
sdsempty
();
while
(
!
done
)
{
if
(
inq
)
{
if
(
*
p
==
'\\'
&&
*
(
p
+
1
))
{
char
c
;
p
++
;
switch
(
*
p
)
{
case
'n'
:
c
=
'\n'
;
break
;
case
'r'
:
c
=
'\r'
;
break
;
case
't'
:
c
=
'\t'
;
break
;
case
'b'
:
c
=
'\b'
;
break
;
case
'a'
:
c
=
'\a'
;
break
;
default:
c
=
*
p
;
break
;
}
current
=
sdscatlen
(
current
,
&
c
,
1
);
}
else
if
(
*
p
==
'"'
)
{
/* closing quote must be followed by a space */
if
(
*
(
p
+
1
)
&&
!
isspace
(
*
(
p
+
1
)))
goto
err
;
done
=
1
;
}
else
if
(
!*
p
)
{
/* unterminated quotes */
goto
err
;
}
else
{
current
=
sdscatlen
(
current
,
p
,
1
);
}
}
else
{
switch
(
*
p
)
{
case
' '
:
case
'\n'
:
case
'\r'
:
case
'\t'
:
case
'\0'
:
done
=
1
;
break
;
case
'"'
:
inq
=
1
;
break
;
default:
current
=
sdscatlen
(
current
,
p
,
1
);
break
;
}
}
if
(
*
p
)
p
++
;
}
/* add the token to the vector */
vector
=
zrealloc
(
vector
,((
*
argc
)
+
1
)
*
sizeof
(
char
*
));
vector
[
*
argc
]
=
current
;
(
*
argc
)
++
;
current
=
NULL
;
}
else
{
return
vector
;
}
}
err:
while
((
*
argc
)
--
)
sdsfree
(
vector
[
*
argc
]);
zfree
(
vector
);
if
(
current
)
sdsfree
(
current
);
return
NULL
;
}
#ifdef SDS_TEST_MAIN
#include <stdio.h>
#include "testhelp.h"
int
main
(
void
)
{
{
sds
x
=
sdsnew
(
"foo"
),
y
;
test_cond
(
"Create a string and obtain the length"
,
sdslen
(
x
)
==
3
&&
memcmp
(
x
,
"foo
\0
"
,
4
)
==
0
)
sdsfree
(
x
);
x
=
sdsnewlen
(
"foo"
,
2
);
test_cond
(
"Create a string with specified length"
,
sdslen
(
x
)
==
2
&&
memcmp
(
x
,
"fo
\0
"
,
3
)
==
0
)
x
=
sdscat
(
x
,
"bar"
);
test_cond
(
"Strings concatenation"
,
sdslen
(
x
)
==
5
&&
memcmp
(
x
,
"fobar
\0
"
,
6
)
==
0
);
x
=
sdscpy
(
x
,
"a"
);
test_cond
(
"sdscpy() against an originally longer string"
,
sdslen
(
x
)
==
1
&&
memcmp
(
x
,
"a
\0
"
,
2
)
==
0
)
x
=
sdscpy
(
x
,
"xyzxxxxxxxxxxyyyyyyyyyykkkkkkkkkk"
);
test_cond
(
"sdscpy() against an originally shorter string"
,
sdslen
(
x
)
==
33
&&
memcmp
(
x
,
"xyzxxxxxxxxxxyyyyyyyyyykkkkkkkkkk
\0
"
,
33
)
==
0
)
sdsfree
(
x
);
x
=
sdscatprintf
(
sdsempty
(),
"%d"
,
123
);
test_cond
(
"sdscatprintf() seems working in the base case"
,
sdslen
(
x
)
==
3
&&
memcmp
(
x
,
"123
\0
"
,
4
)
==
0
)
sdsfree
(
x
);
x
=
sdstrim
(
sdsnew
(
"xxciaoyyy"
),
"xy"
);
test_cond
(
"sdstrim() correctly trims characters"
,
sdslen
(
x
)
==
4
&&
memcmp
(
x
,
"ciao
\0
"
,
5
)
==
0
)
y
=
sdsrange
(
sdsdup
(
x
),
1
,
1
);
test_cond
(
"sdsrange(...,1,1)"
,
sdslen
(
y
)
==
1
&&
memcmp
(
y
,
"i
\0
"
,
2
)
==
0
)
sdsfree
(
y
);
y
=
sdsrange
(
sdsdup
(
x
),
1
,
-
1
);
test_cond
(
"sdsrange(...,1,-1)"
,
sdslen
(
y
)
==
3
&&
memcmp
(
y
,
"iao
\0
"
,
4
)
==
0
)
sdsfree
(
y
);
y
=
sdsrange
(
sdsdup
(
x
),
-
2
,
-
1
);
test_cond
(
"sdsrange(...,-2,-1)"
,
sdslen
(
y
)
==
2
&&
memcmp
(
y
,
"ao
\0
"
,
3
)
==
0
)
sdsfree
(
y
);
y
=
sdsrange
(
sdsdup
(
x
),
2
,
1
);
test_cond
(
"sdsrange(...,2,1)"
,
sdslen
(
y
)
==
0
&&
memcmp
(
y
,
"
\0
"
,
1
)
==
0
)
sdsfree
(
y
);
y
=
sdsrange
(
sdsdup
(
x
),
1
,
100
);
test_cond
(
"sdsrange(...,1,100)"
,
sdslen
(
y
)
==
3
&&
memcmp
(
y
,
"iao
\0
"
,
4
)
==
0
)
sdsfree
(
y
);
y
=
sdsrange
(
sdsdup
(
x
),
100
,
100
);
test_cond
(
"sdsrange(...,100,100)"
,
sdslen
(
y
)
==
0
&&
memcmp
(
y
,
"
\0
"
,
1
)
==
0
)
sdsfree
(
y
);
sdsfree
(
x
);
x
=
sdsnew
(
"foo"
);
y
=
sdsnew
(
"foa"
);
test_cond
(
"sdscmp(foo,foa)"
,
sdscmp
(
x
,
y
)
>
0
)
sdsfree
(
y
);
sdsfree
(
x
);
x
=
sdsnew
(
"bar"
);
y
=
sdsnew
(
"bar"
);
test_cond
(
"sdscmp(bar,bar)"
,
sdscmp
(
x
,
y
)
==
0
)
sdsfree
(
y
);
sdsfree
(
x
);
x
=
sdsnew
(
"aar"
);
y
=
sdsnew
(
"bar"
);
test_cond
(
"sdscmp(bar,bar)"
,
sdscmp
(
x
,
y
)
<
0
)
}
test_report
()
}
#endif
src/sds.h
View file @
b04ce2a3
...
...
@@ -32,6 +32,7 @@
#define __SDS_H
#include <sys/types.h>
#include <stdarg.h>
typedef
char
*
sds
;
...
...
@@ -53,6 +54,7 @@ sds sdscat(sds s, char *t);
sds
sdscpylen
(
sds
s
,
char
*
t
,
size_t
len
);
sds
sdscpy
(
sds
s
,
char
*
t
);
sds
sdscatvprintf
(
sds
s
,
const
char
*
fmt
,
va_list
ap
);
#ifdef __GNUC__
sds
sdscatprintf
(
sds
s
,
const
char
*
fmt
,
...)
__attribute__
((
format
(
printf
,
2
,
3
)));
...
...
@@ -70,5 +72,6 @@ void sdstolower(sds s);
void
sdstoupper
(
sds
s
);
sds
sdsfromlonglong
(
long
long
value
);
sds
sdscatrepr
(
sds
s
,
char
*
p
,
size_t
len
);
sds
*
sdssplitargs
(
char
*
line
,
int
*
argc
);
#endif
src/solarisfixes.h
View file @
b04ce2a3
/* Solaris specific fixes */
#if defined(__GNUC__)
#include <math.h>
#undef isnan
#define isnan(x) \
__extension__({ __typeof (x) __x_a = (x); \
...
...
src/sort.c
View file @
b04ce2a3
...
...
@@ -202,7 +202,7 @@ void sortCommand(redisClient *c) {
/* Load the sorting vector with all the objects to sort */
switch
(
sortval
->
type
)
{
case
REDIS_LIST
:
vectorlen
=
listTypeLength
(
sortval
);
break
;
case
REDIS_SET
:
vectorlen
=
dictSize
((
dict
*
)
sortval
->
ptr
);
break
;
case
REDIS_SET
:
vectorlen
=
setTypeSize
(
sortval
);
break
;
case
REDIS_ZSET
:
vectorlen
=
dictSize
(((
zset
*
)
sortval
->
ptr
)
->
dict
);
break
;
default:
vectorlen
=
0
;
redisPanic
(
"Bad SORT type"
);
/* Avoid GCC warning */
}
...
...
@@ -219,18 +219,20 @@ void sortCommand(redisClient *c) {
j
++
;
}
listTypeReleaseIterator
(
li
);
}
else
{
dict
*
set
;
}
else
if
(
sortval
->
type
==
REDIS_SET
)
{
setTypeIterator
*
si
=
setTypeInitIterator
(
sortval
);
robj
*
ele
;
while
((
ele
=
setTypeNext
(
si
))
!=
NULL
)
{
vector
[
j
].
obj
=
ele
;
vector
[
j
].
u
.
score
=
0
;
vector
[
j
].
u
.
cmpobj
=
NULL
;
j
++
;
}
setTypeReleaseIterator
(
si
);
}
else
if
(
sortval
->
type
==
REDIS_ZSET
)
{
dict
*
set
=
((
zset
*
)
sortval
->
ptr
)
->
dict
;
dictIterator
*
di
;
dictEntry
*
setele
;
if
(
sortval
->
type
==
REDIS_SET
)
{
set
=
sortval
->
ptr
;
}
else
{
zset
*
zs
=
sortval
->
ptr
;
set
=
zs
->
dict
;
}
di
=
dictGetIterator
(
set
);
while
((
setele
=
dictNext
(
di
))
!=
NULL
)
{
vector
[
j
].
obj
=
dictGetEntryKey
(
setele
);
...
...
@@ -239,6 +241,8 @@ void sortCommand(redisClient *c) {
j
++
;
}
dictReleaseIterator
(
di
);
}
else
{
redisPanic
(
"Unknown type"
);
}
redisAssert
(
j
==
vectorlen
);
...
...
@@ -303,7 +307,7 @@ void sortCommand(redisClient *c) {
outputlen
=
getop
?
getop
*
(
end
-
start
+
1
)
:
end
-
start
+
1
;
if
(
storekey
==
NULL
)
{
/* STORE option not specified, sent the sorting result to client */
addReply
Sds
(
c
,
sdscatprintf
(
sdsempty
(),
"*%d
\r\n
"
,
outputlen
)
)
;
addReply
MultiBulkLen
(
c
,
outputlen
);
for
(
j
=
start
;
j
<=
end
;
j
++
)
{
listNode
*
ln
;
listIter
li
;
...
...
@@ -365,11 +369,11 @@ void sortCommand(redisClient *c) {
* replaced. */
server
.
dirty
+=
1
+
outputlen
;
touchWatchedKey
(
c
->
db
,
storekey
);
addReply
Sds
(
c
,
sdscatprintf
(
sdsempty
(),
":%d
\r\n
"
,
outputlen
)
)
;
addReply
LongLong
(
c
,
outputlen
);
}
/* Cleanup */
if
(
sortval
->
type
==
REDIS_LIST
)
if
(
sortval
->
type
==
REDIS_LIST
||
sortval
->
type
==
REDIS_SET
)
for
(
j
=
0
;
j
<
vectorlen
;
j
++
)
decrRefCount
(
vector
[
j
].
obj
);
decrRefCount
(
sortval
);
...
...
src/t_hash.c
View file @
b04ce2a3
...
...
@@ -249,7 +249,7 @@ void hmsetCommand(redisClient *c) {
robj
*
o
;
if
((
c
->
argc
%
2
)
==
1
)
{
addReply
Sds
(
c
,
sdsnew
(
"-ERR
wrong number of arguments for HMSET
\r\n
"
)
);
addReply
Error
(
c
,
"
wrong number of arguments for HMSET
"
);
return
;
}
...
...
@@ -315,7 +315,7 @@ void hmgetCommand(redisClient *c) {
/* Note the check for o != NULL happens inside the loop. This is
* done because objects that cannot be found are considered to be
* an empty hash. The reply should then be a series of NULLs. */
addReply
Sds
(
c
,
sdscatprintf
(
sdsempty
(),
"*%d
\r\n
"
,
c
->
argc
-
2
)
)
;
addReply
MultiBulkLen
(
c
,
c
->
argc
-
2
);
for
(
i
=
2
;
i
<
c
->
argc
;
i
++
)
{
if
(
o
!=
NULL
&&
(
value
=
hashTypeGet
(
o
,
c
->
argv
[
i
]))
!=
NULL
)
{
addReplyBulk
(
c
,
value
);
...
...
@@ -346,21 +346,19 @@ void hlenCommand(redisClient *c) {
if
((
o
=
lookupKeyReadOrReply
(
c
,
c
->
argv
[
1
],
shared
.
czero
))
==
NULL
||
checkType
(
c
,
o
,
REDIS_HASH
))
return
;
addReply
Ul
ong
(
c
,
hashTypeLength
(
o
));
addReply
LongL
ong
(
c
,
hashTypeLength
(
o
));
}
void
genericHgetallCommand
(
redisClient
*
c
,
int
flags
)
{
robj
*
o
,
*
lenobj
,
*
obj
;
robj
*
o
,
*
obj
;
unsigned
long
count
=
0
;
hashTypeIterator
*
hi
;
void
*
replylen
=
NULL
;
if
((
o
=
lookupKeyReadOrReply
(
c
,
c
->
argv
[
1
],
shared
.
emptymultibulk
))
==
NULL
||
checkType
(
c
,
o
,
REDIS_HASH
))
return
;
lenobj
=
createObject
(
REDIS_STRING
,
NULL
);
addReply
(
c
,
lenobj
);
decrRefCount
(
lenobj
);
replylen
=
addDeferredMultiBulkLength
(
c
);
hi
=
hashTypeInitIterator
(
o
);
while
(
hashTypeNext
(
hi
)
!=
REDIS_ERR
)
{
if
(
flags
&
REDIS_HASH_KEY
)
{
...
...
@@ -377,8 +375,7 @@ void genericHgetallCommand(redisClient *c, int flags) {
}
}
hashTypeReleaseIterator
(
hi
);
lenobj
->
ptr
=
sdscatprintf
(
sdsempty
(),
"*%lu
\r\n
"
,
count
);
setDeferredMultiBulkLength
(
c
,
replylen
,
count
);
}
void
hkeysCommand
(
redisClient
*
c
)
{
...
...
src/t_list.c
View file @
b04ce2a3
...
...
@@ -342,7 +342,7 @@ void pushxGenericCommand(redisClient *c, robj *refval, robj *val, int where) {
server
.
dirty
++
;
}
addReply
Ul
ong
(
c
,
listTypeLength
(
subject
));
addReply
LongL
ong
(
c
,
listTypeLength
(
subject
));
}
void
lpushxCommand
(
redisClient
*
c
)
{
...
...
@@ -366,7 +366,7 @@ void linsertCommand(redisClient *c) {
void
llenCommand
(
redisClient
*
c
)
{
robj
*
o
=
lookupKeyReadOrReply
(
c
,
c
->
argv
[
1
],
shared
.
czero
);
if
(
o
==
NULL
||
checkType
(
c
,
o
,
REDIS_LIST
))
return
;
addReply
Ul
ong
(
c
,
listTypeLength
(
o
));
addReply
LongL
ong
(
c
,
listTypeLength
(
o
));
}
void
lindexCommand
(
redisClient
*
c
)
{
...
...
@@ -494,7 +494,7 @@ void lrangeCommand(redisClient *c) {
rangelen
=
(
end
-
start
)
+
1
;
/* Return the result in form of a multi-bulk reply */
addReply
Sds
(
c
,
sdscatprintf
(
sdsempty
(),
"*%d
\r\n
"
,
rangelen
)
)
;
addReply
MultiBulkLen
(
c
,
rangelen
);
listTypeIterator
*
li
=
listTypeInitIterator
(
o
,
start
,
REDIS_TAIL
);
for
(
j
=
0
;
j
<
rangelen
;
j
++
)
{
redisAssert
(
listTypeNext
(
li
,
&
entry
));
...
...
@@ -594,7 +594,7 @@ void lremCommand(redisClient *c) {
decrRefCount
(
obj
);
if
(
listTypeLength
(
subject
)
==
0
)
dbDelete
(
c
->
db
,
c
->
argv
[
1
]);
addReply
Sds
(
c
,
sdscatprintf
(
sdsempty
(),
":%d
\r\n
"
,
removed
)
)
;
addReply
LongLong
(
c
,
removed
);
if
(
removed
)
touchWatchedKey
(
c
->
db
,
c
->
argv
[
1
]);
}
...
...
@@ -772,7 +772,7 @@ int handleClientsWaitingListPush(redisClient *c, robj *key, robj *ele) {
redisAssert
(
ln
!=
NULL
);
receiver
=
ln
->
value
;
addReply
Sds
(
receiver
,
sdsnew
(
"*2
\r\n
"
)
);
addReply
MultiBulkLen
(
receiver
,
2
);
addReplyBulk
(
receiver
,
key
);
addReplyBulk
(
receiver
,
ele
);
unblockClientWaitingData
(
receiver
);
...
...
@@ -782,9 +782,20 @@ int handleClientsWaitingListPush(redisClient *c, robj *key, robj *ele) {
/* Blocking RPOP/LPOP */
void
blockingPopGenericCommand
(
redisClient
*
c
,
int
where
)
{
robj
*
o
;
long
long
lltimeout
;
time_t
timeout
;
int
j
;
/* Make sure timeout is an integer value */
if
(
getLongLongFromObjectOrReply
(
c
,
c
->
argv
[
c
->
argc
-
1
],
&
lltimeout
,
"timeout is not an integer"
)
!=
REDIS_OK
)
return
;
/* Make sure the timeout is not negative */
if
(
lltimeout
<
0
)
{
addReplyError
(
c
,
"timeout is negative"
);
return
;
}
for
(
j
=
1
;
j
<
c
->
argc
-
1
;
j
++
)
{
o
=
lookupKeyWrite
(
c
->
db
,
c
->
argv
[
j
]);
if
(
o
!=
NULL
)
{
...
...
@@ -811,7 +822,7 @@ void blockingPopGenericCommand(redisClient *c, int where) {
* "real" command will add the last element (the value)
* for us. If this souds like an hack to you it's just
* because it is... */
addReply
Sds
(
c
,
sdsnew
(
"*2
\r\n
"
)
);
addReply
MultiBulkLen
(
c
,
2
);
addReplyBulk
(
c
,
argv
[
1
]);
popGenericCommand
(
c
,
where
);
...
...
@@ -823,8 +834,16 @@ void blockingPopGenericCommand(redisClient *c, int where) {
}
}
}
/* If we are inside a MULTI/EXEC and the list is empty the only thing
* we can do is treating it as a timeout (even with timeout 0). */
if
(
c
->
flags
&
REDIS_MULTI
)
{
addReply
(
c
,
shared
.
nullmultibulk
);
return
;
}
/* If the list is empty or the key does not exists we must block */
timeout
=
strtol
(
c
->
argv
[
c
->
argc
-
1
]
->
ptr
,
NULL
,
10
)
;
timeout
=
lltimeout
;
if
(
timeout
>
0
)
timeout
+=
time
(
NULL
);
blockForKeys
(
c
,
c
->
argv
+
1
,
c
->
argc
-
2
,
timeout
);
}
...
...
src/t_set.c
View file @
b04ce2a3
...
...
@@ -4,12 +4,182 @@
* Set Commands
*----------------------------------------------------------------------------*/
/* Factory method to return a set that *can* hold "value". When the object has
* an integer-encodable value, an intset will be returned. Otherwise a regular
* hash table. */
robj
*
setTypeCreate
(
robj
*
value
)
{
if
(
isObjectRepresentableAsLongLong
(
value
,
NULL
)
==
REDIS_OK
)
return
createIntsetObject
();
return
createSetObject
();
}
int
setTypeAdd
(
robj
*
subject
,
robj
*
value
)
{
long
long
llval
;
if
(
subject
->
encoding
==
REDIS_ENCODING_HT
)
{
if
(
dictAdd
(
subject
->
ptr
,
value
,
NULL
)
==
DICT_OK
)
{
incrRefCount
(
value
);
return
1
;
}
}
else
if
(
subject
->
encoding
==
REDIS_ENCODING_INTSET
)
{
if
(
isObjectRepresentableAsLongLong
(
value
,
&
llval
)
==
REDIS_OK
)
{
uint8_t
success
=
0
;
subject
->
ptr
=
intsetAdd
(
subject
->
ptr
,
llval
,
&
success
);
if
(
success
)
{
/* Convert to regular set when the intset contains
* too many entries. */
if
(
intsetLen
(
subject
->
ptr
)
>
server
.
set_max_intset_entries
)
setTypeConvert
(
subject
,
REDIS_ENCODING_HT
);
return
1
;
}
}
else
{
/* Failed to get integer from object, convert to regular set. */
setTypeConvert
(
subject
,
REDIS_ENCODING_HT
);
/* The set *was* an intset and this value is not integer
* encodable, so dictAdd should always work. */
redisAssert
(
dictAdd
(
subject
->
ptr
,
value
,
NULL
)
==
DICT_OK
);
incrRefCount
(
value
);
return
1
;
}
}
else
{
redisPanic
(
"Unknown set encoding"
);
}
return
0
;
}
int
setTypeRemove
(
robj
*
subject
,
robj
*
value
)
{
long
long
llval
;
if
(
subject
->
encoding
==
REDIS_ENCODING_HT
)
{
if
(
dictDelete
(
subject
->
ptr
,
value
)
==
DICT_OK
)
{
if
(
htNeedsResize
(
subject
->
ptr
))
dictResize
(
subject
->
ptr
);
return
1
;
}
}
else
if
(
subject
->
encoding
==
REDIS_ENCODING_INTSET
)
{
if
(
isObjectRepresentableAsLongLong
(
value
,
&
llval
)
==
REDIS_OK
)
{
uint8_t
success
;
subject
->
ptr
=
intsetRemove
(
subject
->
ptr
,
llval
,
&
success
);
if
(
success
)
return
1
;
}
}
else
{
redisPanic
(
"Unknown set encoding"
);
}
return
0
;
}
int
setTypeIsMember
(
robj
*
subject
,
robj
*
value
)
{
long
long
llval
;
if
(
subject
->
encoding
==
REDIS_ENCODING_HT
)
{
return
dictFind
((
dict
*
)
subject
->
ptr
,
value
)
!=
NULL
;
}
else
if
(
subject
->
encoding
==
REDIS_ENCODING_INTSET
)
{
if
(
isObjectRepresentableAsLongLong
(
value
,
&
llval
)
==
REDIS_OK
)
{
return
intsetFind
((
intset
*
)
subject
->
ptr
,
llval
);
}
}
else
{
redisPanic
(
"Unknown set encoding"
);
}
return
0
;
}
setTypeIterator
*
setTypeInitIterator
(
robj
*
subject
)
{
setTypeIterator
*
si
=
zmalloc
(
sizeof
(
setTypeIterator
));
si
->
subject
=
subject
;
si
->
encoding
=
subject
->
encoding
;
if
(
si
->
encoding
==
REDIS_ENCODING_HT
)
{
si
->
di
=
dictGetIterator
(
subject
->
ptr
);
}
else
if
(
si
->
encoding
==
REDIS_ENCODING_INTSET
)
{
si
->
ii
=
0
;
}
else
{
redisPanic
(
"Unknown set encoding"
);
}
return
si
;
}
void
setTypeReleaseIterator
(
setTypeIterator
*
si
)
{
if
(
si
->
encoding
==
REDIS_ENCODING_HT
)
dictReleaseIterator
(
si
->
di
);
zfree
(
si
);
}
/* Move to the next entry in the set. Returns the object at the current
* position, or NULL when the end is reached. This object will have its
* refcount incremented, so the caller needs to take care of this. */
robj
*
setTypeNext
(
setTypeIterator
*
si
)
{
robj
*
ret
=
NULL
;
if
(
si
->
encoding
==
REDIS_ENCODING_HT
)
{
dictEntry
*
de
=
dictNext
(
si
->
di
);
if
(
de
!=
NULL
)
{
ret
=
dictGetEntryKey
(
de
);
incrRefCount
(
ret
);
}
}
else
if
(
si
->
encoding
==
REDIS_ENCODING_INTSET
)
{
int64_t
llval
;
if
(
intsetGet
(
si
->
subject
->
ptr
,
si
->
ii
++
,
&
llval
))
ret
=
createStringObjectFromLongLong
(
llval
);
}
return
ret
;
}
/* Return random element from set. The returned object will always have
* an incremented refcount. */
robj
*
setTypeRandomElement
(
robj
*
subject
)
{
robj
*
ret
=
NULL
;
if
(
subject
->
encoding
==
REDIS_ENCODING_HT
)
{
dictEntry
*
de
=
dictGetRandomKey
(
subject
->
ptr
);
ret
=
dictGetEntryKey
(
de
);
incrRefCount
(
ret
);
}
else
if
(
subject
->
encoding
==
REDIS_ENCODING_INTSET
)
{
long
long
llval
=
intsetRandom
(
subject
->
ptr
);
ret
=
createStringObjectFromLongLong
(
llval
);
}
else
{
redisPanic
(
"Unknown set encoding"
);
}
return
ret
;
}
unsigned
long
setTypeSize
(
robj
*
subject
)
{
if
(
subject
->
encoding
==
REDIS_ENCODING_HT
)
{
return
dictSize
((
dict
*
)
subject
->
ptr
);
}
else
if
(
subject
->
encoding
==
REDIS_ENCODING_INTSET
)
{
return
intsetLen
((
intset
*
)
subject
->
ptr
);
}
else
{
redisPanic
(
"Unknown set encoding"
);
}
}
/* Convert the set to specified encoding. The resulting dict (when converting
* to a hashtable) is presized to hold the number of elements in the original
* set. */
void
setTypeConvert
(
robj
*
subject
,
int
enc
)
{
setTypeIterator
*
si
;
robj
*
element
;
redisAssert
(
subject
->
type
==
REDIS_SET
);
if
(
enc
==
REDIS_ENCODING_HT
)
{
dict
*
d
=
dictCreate
(
&
setDictType
,
NULL
);
/* Presize the dict to avoid rehashing */
dictExpand
(
d
,
intsetLen
(
subject
->
ptr
));
/* setTypeGet returns a robj with incremented refcount */
si
=
setTypeInitIterator
(
subject
);
while
((
element
=
setTypeNext
(
si
))
!=
NULL
)
redisAssert
(
dictAdd
(
d
,
element
,
NULL
)
==
DICT_OK
);
setTypeReleaseIterator
(
si
);
subject
->
encoding
=
REDIS_ENCODING_HT
;
zfree
(
subject
->
ptr
);
subject
->
ptr
=
d
;
}
else
{
redisPanic
(
"Unsupported set conversion"
);
}
}
void
saddCommand
(
redisClient
*
c
)
{
robj
*
set
;
set
=
lookupKeyWrite
(
c
->
db
,
c
->
argv
[
1
]);
if
(
set
==
NULL
)
{
set
=
createSetObject
(
);
set
=
setTypeCreate
(
c
->
argv
[
2
]
);
dbAdd
(
c
->
db
,
c
->
argv
[
1
],
set
);
}
else
{
if
(
set
->
type
!=
REDIS_SET
)
{
...
...
@@ -17,8 +187,7 @@ void saddCommand(redisClient *c) {
return
;
}
}
if
(
dictAdd
(
set
->
ptr
,
c
->
argv
[
2
],
NULL
)
==
DICT_OK
)
{
incrRefCount
(
c
->
argv
[
2
]);
if
(
setTypeAdd
(
set
,
c
->
argv
[
2
]))
{
touchWatchedKey
(
c
->
db
,
c
->
argv
[
1
]);
server
.
dirty
++
;
addReply
(
c
,
shared
.
cone
);
...
...
@@ -33,11 +202,10 @@ void sremCommand(redisClient *c) {
if
((
set
=
lookupKeyWriteOrReply
(
c
,
c
->
argv
[
1
],
shared
.
czero
))
==
NULL
||
checkType
(
c
,
set
,
REDIS_SET
))
return
;
if
(
dictDelete
(
set
->
ptr
,
c
->
argv
[
2
])
==
DICT_OK
)
{
server
.
dirty
++
;
if
(
setTypeRemove
(
set
,
c
->
argv
[
2
]))
{
if
(
setTypeSize
(
set
)
==
0
)
dbDelete
(
c
->
db
,
c
->
argv
[
1
])
;
touchWatchedKey
(
c
->
db
,
c
->
argv
[
1
]);
if
(
htNeedsResize
(
set
->
ptr
))
dictResize
(
set
->
ptr
);
if
(
dictSize
((
dict
*
)
set
->
ptr
)
==
0
)
dbDelete
(
c
->
db
,
c
->
argv
[
1
]);
server
.
dirty
++
;
addReply
(
c
,
shared
.
cone
);
}
else
{
addReply
(
c
,
shared
.
czero
);
...
...
@@ -45,40 +213,48 @@ void sremCommand(redisClient *c) {
}
void
smoveCommand
(
redisClient
*
c
)
{
robj
*
srcset
,
*
dstset
;
robj
*
srcset
,
*
dstset
,
*
ele
;
srcset
=
lookupKeyWrite
(
c
->
db
,
c
->
argv
[
1
]);
dstset
=
lookupKeyWrite
(
c
->
db
,
c
->
argv
[
2
]);
ele
=
c
->
argv
[
3
];
/* If the source key does not exist return 0, if it's of the wrong type
* raise an error */
if
(
srcset
==
NULL
||
srcset
->
type
!=
REDIS_SET
)
{
addReply
(
c
,
srcset
?
shared
.
wrongtypeerr
:
shared
.
czero
);
/* If the source key does not exist return 0 */
if
(
srcset
==
NULL
)
{
addReply
(
c
,
shared
.
czero
);
return
;
}
/* Error if the destination key is not a set as well */
if
(
dstset
&&
dstset
->
type
!=
REDIS_SET
)
{
addReply
(
c
,
shared
.
wrongtypeerr
);
/* If the source key has the wrong type, or the destination key
* is set and has the wrong type, return with an error. */
if
(
checkType
(
c
,
srcset
,
REDIS_SET
)
||
(
dstset
&&
checkType
(
c
,
dstset
,
REDIS_SET
)))
return
;
/* If srcset and dstset are equal, SMOVE is a no-op */
if
(
srcset
==
dstset
)
{
addReply
(
c
,
shared
.
cone
);
return
;
}
/* Remove the element from the source set */
if
(
dictDelete
(
srcset
->
ptr
,
c
->
argv
[
3
])
==
DICT_ERR
)
{
/* Key not found in the
src
set
! return zero */
/* If the element cannot be removed from the
src
set
, return 0. */
if
(
!
setTypeRemove
(
srcset
,
ele
))
{
addReply
(
c
,
shared
.
czero
);
return
;
}
if
(
dictSize
((
dict
*
)
srcset
->
ptr
)
==
0
&&
srcset
!=
dstset
)
dbDelete
(
c
->
db
,
c
->
argv
[
1
]);
/* Remove the src set from the database when empty */
if
(
setTypeSize
(
srcset
)
==
0
)
dbDelete
(
c
->
db
,
c
->
argv
[
1
]);
touchWatchedKey
(
c
->
db
,
c
->
argv
[
1
]);
touchWatchedKey
(
c
->
db
,
c
->
argv
[
2
]);
server
.
dirty
++
;
/* Add the element to the destination set */
/* Create the destination set when it doesn't exist */
if
(
!
dstset
)
{
dstset
=
createSetObject
(
);
dstset
=
setTypeCreate
(
ele
);
dbAdd
(
c
->
db
,
c
->
argv
[
2
],
dstset
);
}
if
(
dictAdd
(
dstset
->
ptr
,
c
->
argv
[
3
],
NULL
)
==
DICT_OK
)
incrRefCount
(
c
->
argv
[
3
]);
/* An extra key has changed when ele was successfully added to dstset */
if
(
setTypeAdd
(
dstset
,
ele
))
server
.
dirty
++
;
addReply
(
c
,
shared
.
cone
);
}
...
...
@@ -88,7 +264,7 @@ void sismemberCommand(redisClient *c) {
if
((
set
=
lookupKeyReadOrReply
(
c
,
c
->
argv
[
1
],
shared
.
czero
))
==
NULL
||
checkType
(
c
,
set
,
REDIS_SET
))
return
;
if
(
dictFind
(
set
->
ptr
,
c
->
argv
[
2
]))
if
(
setTypeIsMember
(
set
,
c
->
argv
[
2
]))
addReply
(
c
,
shared
.
cone
);
else
addReply
(
c
,
shared
.
czero
);
...
...
@@ -96,75 +272,64 @@ void sismemberCommand(redisClient *c) {
void
scardCommand
(
redisClient
*
c
)
{
robj
*
o
;
dict
*
s
;
if
((
o
=
lookupKeyReadOrReply
(
c
,
c
->
argv
[
1
],
shared
.
czero
))
==
NULL
||
checkType
(
c
,
o
,
REDIS_SET
))
return
;
s
=
o
->
ptr
;
addReplyUlong
(
c
,
dictSize
(
s
));
addReplyLongLong
(
c
,
setTypeSize
(
o
));
}
void
spopCommand
(
redisClient
*
c
)
{
robj
*
set
;
dictEntry
*
de
;
robj
*
set
,
*
ele
;
if
((
set
=
lookupKeyWriteOrReply
(
c
,
c
->
argv
[
1
],
shared
.
nullbulk
))
==
NULL
||
checkType
(
c
,
set
,
REDIS_SET
))
return
;
d
e
=
dictGetRandomKey
(
set
->
ptr
);
if
(
d
e
==
NULL
)
{
el
e
=
setTypeRandomElement
(
set
);
if
(
el
e
==
NULL
)
{
addReply
(
c
,
shared
.
nullbulk
);
}
else
{
robj
*
ele
=
dictGetEntryKey
(
de
);
setTypeRemove
(
set
,
ele
);
addReplyBulk
(
c
,
ele
);
dictDelete
(
set
->
ptr
,
ele
);
if
(
htNeedsResize
(
set
->
ptr
))
dictResize
(
set
->
ptr
);
if
(
dictSize
((
dict
*
)
set
->
ptr
)
==
0
)
dbDelete
(
c
->
db
,
c
->
argv
[
1
]);
decrRefCount
(
ele
);
if
(
setTypeSize
(
set
)
==
0
)
dbDelete
(
c
->
db
,
c
->
argv
[
1
]);
touchWatchedKey
(
c
->
db
,
c
->
argv
[
1
]);
server
.
dirty
++
;
}
}
void
srandmemberCommand
(
redisClient
*
c
)
{
robj
*
set
;
dictEntry
*
de
;
robj
*
set
,
*
ele
;
if
((
set
=
lookupKeyReadOrReply
(
c
,
c
->
argv
[
1
],
shared
.
nullbulk
))
==
NULL
||
checkType
(
c
,
set
,
REDIS_SET
))
return
;
d
e
=
dictGetRandomKey
(
set
->
ptr
);
if
(
d
e
==
NULL
)
{
el
e
=
setTypeRandomElement
(
set
);
if
(
el
e
==
NULL
)
{
addReply
(
c
,
shared
.
nullbulk
);
}
else
{
robj
*
ele
=
dictGetEntryKey
(
de
);
addReplyBulk
(
c
,
ele
);
decrRefCount
(
ele
);
}
}
int
qsortCompareSetsByCardinality
(
const
void
*
s1
,
const
void
*
s2
)
{
dict
**
d1
=
(
void
*
)
s1
,
**
d2
=
(
void
*
)
s2
;
return
dictSize
(
*
d1
)
-
dictSize
(
*
d2
);
return
setTypeSize
(
*
(
robj
**
)
s1
)
-
setTypeSize
(
*
(
robj
**
)
s2
);
}
void
sinterGenericCommand
(
redisClient
*
c
,
robj
**
set
s
keys
,
unsigned
long
set
s
num
,
robj
*
dstkey
)
{
dict
**
dv
=
zmalloc
(
sizeof
(
dict
*
)
*
set
s
num
);
dict
Iterator
*
d
i
;
dictEntry
*
de
;
robj
*
lenobj
=
NULL
,
*
dstset
=
NULL
;
void
sinterGenericCommand
(
redisClient
*
c
,
robj
**
setkeys
,
unsigned
long
setnum
,
robj
*
dstkey
)
{
robj
**
sets
=
zmalloc
(
sizeof
(
robj
*
)
*
setnum
);
setType
Iterator
*
s
i
;
robj
*
ele
,
*
dstset
=
NULL
;
void
*
replylen
=
NULL
;
unsigned
long
j
,
cardinality
=
0
;
for
(
j
=
0
;
j
<
setsnum
;
j
++
)
{
robj
*
setobj
;
setobj
=
dstkey
?
lookupKeyWrite
(
c
->
db
,
setskeys
[
j
])
:
lookupKeyRead
(
c
->
db
,
setskeys
[
j
]);
for
(
j
=
0
;
j
<
setnum
;
j
++
)
{
robj
*
setobj
=
dstkey
?
lookupKeyWrite
(
c
->
db
,
setkeys
[
j
])
:
lookupKeyRead
(
c
->
db
,
setkeys
[
j
]);
if
(
!
setobj
)
{
zfree
(
dv
);
zfree
(
sets
);
if
(
dstkey
)
{
if
(
dbDelete
(
c
->
db
,
dstkey
))
{
touchWatchedKey
(
c
->
db
,
dstkey
);
...
...
@@ -176,16 +341,15 @@ void sinterGenericCommand(redisClient *c, robj **setskeys, unsigned long setsnum
}
return
;
}
if
(
setobj
->
type
!=
REDIS_SET
)
{
zfree
(
dv
);
addReply
(
c
,
shared
.
wrongtypeerr
);
if
(
checkType
(
c
,
setobj
,
REDIS_SET
))
{
zfree
(
sets
);
return
;
}
dv
[
j
]
=
setobj
->
ptr
;
sets
[
j
]
=
setobj
;
}
/* Sort sets from the smallest to largest, this will improve our
* algorithm's performace */
qsort
(
dv
,
set
s
num
,
sizeof
(
dict
*
),
qsortCompareSetsByCardinality
);
qsort
(
sets
,
setnum
,
sizeof
(
robj
*
),
qsortCompareSetsByCardinality
);
/* The first thing we should output is the total number of elements...
* since this is a multi-bulk write, but at this stage we don't know
...
...
@@ -193,45 +357,41 @@ void sinterGenericCommand(redisClient *c, robj **setskeys, unsigned long setsnum
* to the output list and save the pointer to later modify it with the
* right length */
if
(
!
dstkey
)
{
lenobj
=
createObject
(
REDIS_STRING
,
NULL
);
addReply
(
c
,
lenobj
);
decrRefCount
(
lenobj
);
replylen
=
addDeferredMultiBulkLength
(
c
);
}
else
{
/* If we have a target key where to store the resulting set
* create this key with an empty set inside */
dstset
=
create
S
etObject
();
dstset
=
create
Ints
etObject
();
}
/* Iterate all the elements of the first (smallest) set, and test
* the element against all the other sets, if at least one set does
* not include the element it is discarded */
di
=
dictGetIterator
(
dv
[
0
]);
si
=
setTypeInitIterator
(
sets
[
0
]);
while
((
ele
=
setTypeNext
(
si
))
!=
NULL
)
{
for
(
j
=
1
;
j
<
setnum
;
j
++
)
if
(
!
setTypeIsMember
(
sets
[
j
],
ele
))
break
;
while
((
de
=
dictNext
(
di
))
!=
NULL
)
{
robj
*
ele
;
for
(
j
=
1
;
j
<
setsnum
;
j
++
)
if
(
dictFind
(
dv
[
j
],
dictGetEntryKey
(
de
))
==
NULL
)
break
;
if
(
j
!=
setsnum
)
continue
;
/* at least one set does not contain the member */
ele
=
dictGetEntryKey
(
de
);
/* Only take action when all sets contain the member */
if
(
j
==
setnum
)
{
if
(
!
dstkey
)
{
addReplyBulk
(
c
,
ele
);
cardinality
++
;
}
else
{
dictAdd
(
dstset
->
ptr
,
ele
,
NULL
);
incrRefCount
(
ele
);
setTypeAdd
(
dstset
,
ele
);
}
}
decrRefCount
(
ele
);
}
dict
ReleaseIterator
(
d
i
);
setType
ReleaseIterator
(
s
i
);
if
(
dstkey
)
{
/* Store the resulting set into the target, if the intersection
* is not an empty set. */
dbDelete
(
c
->
db
,
dstkey
);
if
(
dictSize
((
dict
*
)
dstset
->
ptr
)
>
0
)
{
if
(
setTypeSize
(
dstset
)
>
0
)
{
dbAdd
(
c
->
db
,
dstkey
,
dstset
);
addReplyLongLong
(
c
,
dictSize
((
dict
*
)
dstset
->
ptr
));
addReplyLongLong
(
c
,
setTypeSize
(
dstset
));
}
else
{
decrRefCount
(
dstset
);
addReply
(
c
,
shared
.
czero
);
...
...
@@ -239,9 +399,9 @@ void sinterGenericCommand(redisClient *c, robj **setskeys, unsigned long setsnum
touchWatchedKey
(
c
->
db
,
dstkey
);
server
.
dirty
++
;
}
else
{
lenobj
->
ptr
=
sdscatprintf
(
sdsempty
(),
"*%lu
\r\n
"
,
cardinality
);
setDeferredMultiBulkLength
(
c
,
replylen
,
cardinality
);
}
zfree
(
dv
);
zfree
(
sets
);
}
void
sinterCommand
(
redisClient
*
c
)
{
...
...
@@ -252,85 +412,78 @@ void sinterstoreCommand(redisClient *c) {
sinterGenericCommand
(
c
,
c
->
argv
+
2
,
c
->
argc
-
2
,
c
->
argv
[
1
]);
}
void
sunionDiffGenericCommand
(
redisClient
*
c
,
robj
**
setskeys
,
int
setsnum
,
robj
*
dstkey
,
int
op
)
{
dict
**
dv
=
zmalloc
(
sizeof
(
dict
*
)
*
setsnum
);
dictIterator
*
di
;
dictEntry
*
de
;
robj
*
dstset
=
NULL
;
int
j
,
cardinality
=
0
;
#define REDIS_OP_UNION 0
#define REDIS_OP_DIFF 1
#define REDIS_OP_INTER 2
for
(
j
=
0
;
j
<
setsnum
;
j
++
)
{
robj
*
setobj
;
void
sunionDiffGenericCommand
(
redisClient
*
c
,
robj
**
setkeys
,
int
setnum
,
robj
*
dstkey
,
int
op
)
{
robj
**
sets
=
zmalloc
(
sizeof
(
robj
*
)
*
setnum
);
setTypeIterator
*
si
;
robj
*
ele
,
*
dstset
=
NULL
;
int
j
,
cardinality
=
0
;
setobj
=
dstkey
?
lookupKeyWrite
(
c
->
db
,
setskeys
[
j
])
:
lookupKeyRead
(
c
->
db
,
setskeys
[
j
]);
for
(
j
=
0
;
j
<
setnum
;
j
++
)
{
robj
*
setobj
=
dstkey
?
lookupKeyWrite
(
c
->
db
,
setkeys
[
j
])
:
lookupKeyRead
(
c
->
db
,
setkeys
[
j
]);
if
(
!
setobj
)
{
dv
[
j
]
=
NULL
;
sets
[
j
]
=
NULL
;
continue
;
}
if
(
setobj
->
type
!=
REDIS_SET
)
{
zfree
(
dv
);
addReply
(
c
,
shared
.
wrongtypeerr
);
if
(
checkType
(
c
,
setobj
,
REDIS_SET
))
{
zfree
(
sets
);
return
;
}
dv
[
j
]
=
setobj
->
ptr
;
sets
[
j
]
=
setobj
;
}
/* We need a temp set object to store our union. If the dstkey
* is not NULL (that is, we are inside an SUNIONSTORE operation) then
* this set object will be the resulting object to set into the target key*/
dstset
=
create
S
etObject
();
dstset
=
create
Ints
etObject
();
/* Iterate all the elements of all the sets, add every element a single
* time to the result set */
for
(
j
=
0
;
j
<
setsnum
;
j
++
)
{
if
(
op
==
REDIS_OP_DIFF
&&
j
==
0
&&
!
dv
[
j
])
break
;
/* result set is empty */
if
(
!
dv
[
j
])
continue
;
/* non existing keys are like empty sets */
di
=
dictGetIterator
(
dv
[
j
]);
for
(
j
=
0
;
j
<
setnum
;
j
++
)
{
if
(
op
==
REDIS_OP_DIFF
&&
j
==
0
&&
!
sets
[
j
])
break
;
/* result set is empty */
if
(
!
sets
[
j
])
continue
;
/* non existing keys are like empty sets */
while
((
de
=
dictNext
(
di
))
!=
NULL
)
{
robj
*
ele
;
/* dictAdd will not add the same element multiple times */
ele
=
dictGetEntryKey
(
de
);
si
=
setTypeInitIterator
(
sets
[
j
]);
while
((
ele
=
setTypeNext
(
si
))
!=
NULL
)
{
if
(
op
==
REDIS_OP_UNION
||
j
==
0
)
{
if
(
dictAdd
(
dstset
->
ptr
,
ele
,
NULL
)
==
DICT_OK
)
{
incrRefCount
(
ele
);
if
(
setTypeAdd
(
dstset
,
ele
))
{
cardinality
++
;
}
}
else
if
(
op
==
REDIS_OP_DIFF
)
{
if
(
dictDelete
(
dstset
->
ptr
,
ele
)
==
DICT_OK
)
{
if
(
setTypeRemove
(
dstset
,
ele
)
)
{
cardinality
--
;
}
}
decrRefCount
(
ele
);
}
dict
ReleaseIterator
(
d
i
);
setType
ReleaseIterator
(
s
i
);
/* result set is empty
? Exit asap
. */
/*
Exit when
result set is empty. */
if
(
op
==
REDIS_OP_DIFF
&&
cardinality
==
0
)
break
;
}
/* Output the content of the resulting set, if not in STORE mode */
if
(
!
dstkey
)
{
addReplySds
(
c
,
sdscatprintf
(
sdsempty
(),
"*%d
\r\n
"
,
cardinality
));
di
=
dictGetIterator
(
dstset
->
ptr
);
while
((
de
=
dictNext
(
di
))
!=
NULL
)
{
robj
*
ele
;
ele
=
dictGetEntryKey
(
de
);
addReplyMultiBulkLen
(
c
,
cardinality
);
si
=
setTypeInitIterator
(
dstset
);
while
((
ele
=
setTypeNext
(
si
))
!=
NULL
)
{
addReplyBulk
(
c
,
ele
);
decrRefCount
(
ele
);
}
dict
ReleaseIterator
(
d
i
);
setType
ReleaseIterator
(
s
i
);
decrRefCount
(
dstset
);
}
else
{
/* If we have a target key where to store the resulting set
* create this key with the result set inside */
dbDelete
(
c
->
db
,
dstkey
);
if
(
dictSize
((
dict
*
)
dstset
->
ptr
)
>
0
)
{
if
(
setTypeSize
(
dstset
)
>
0
)
{
dbAdd
(
c
->
db
,
dstkey
,
dstset
);
addReplyLongLong
(
c
,
dictSize
((
dict
*
)
dstset
->
ptr
));
addReplyLongLong
(
c
,
setTypeSize
(
dstset
));
}
else
{
decrRefCount
(
dstset
);
addReply
(
c
,
shared
.
czero
);
...
...
@@ -338,7 +491,7 @@ void sunionDiffGenericCommand(redisClient *c, robj **setskeys, int setsnum, robj
touchWatchedKey
(
c
->
db
,
dstkey
);
server
.
dirty
++
;
}
zfree
(
dv
);
zfree
(
sets
);
}
void
sunionCommand
(
redisClient
*
c
)
{
...
...
src/t_string.c
View file @
b04ce2a3
...
...
@@ -12,12 +12,11 @@ void setGenericCommand(redisClient *c, int nx, robj *key, robj *val, robj *expir
if
(
getLongFromObjectOrReply
(
c
,
expire
,
&
seconds
,
NULL
)
!=
REDIS_OK
)
return
;
if
(
seconds
<=
0
)
{
addReply
Sds
(
c
,
sdsnew
(
"-ERR
invalid expire time in SETEX
\r\n
"
)
);
addReply
Error
(
c
,
"
invalid expire time in SETEX
"
);
return
;
}
}
if
(
nx
)
deleteIfVolatile
(
c
->
db
,
key
);
retval
=
dbAdd
(
c
->
db
,
key
,
val
);
if
(
retval
==
REDIS_ERR
)
{
if
(
!
nx
)
{
...
...
@@ -80,7 +79,7 @@ void getsetCommand(redisClient *c) {
void
mgetCommand
(
redisClient
*
c
)
{
int
j
;
addReply
Sds
(
c
,
sdscatprintf
(
sdsempty
(),
"*%d
\r\n
"
,
c
->
argc
-
1
)
)
;
addReply
MultiBulkLen
(
c
,
c
->
argc
-
1
);
for
(
j
=
1
;
j
<
c
->
argc
;
j
++
)
{
robj
*
o
=
lookupKeyRead
(
c
->
db
,
c
->
argv
[
j
]);
if
(
o
==
NULL
)
{
...
...
@@ -99,7 +98,7 @@ void msetGenericCommand(redisClient *c, int nx) {
int
j
,
busykeys
=
0
;
if
((
c
->
argc
%
2
)
==
0
)
{
addReply
Sds
(
c
,
sdsnew
(
"-ERR
wrong number of arguments for MSET
\r\n
"
)
);
addReply
Error
(
c
,
"
wrong number of arguments for MSET
"
);
return
;
}
/* Handle the NX flag. The MSETNX semantic is to return zero and don't
...
...
@@ -212,7 +211,7 @@ void appendCommand(redisClient *c) {
}
touchWatchedKey
(
c
->
db
,
c
->
argv
[
1
]);
server
.
dirty
++
;
addReply
Sds
(
c
,
sdscatprintf
(
sdsempty
(),
":%lu
\r\n
"
,(
unsigned
long
)
totlen
)
)
;
addReply
LongLong
(
c
,
totlen
);
}
void
substrCommand
(
redisClient
*
c
)
{
...
...
src/t_zset.c
View file @
b04ce2a3
...
...
@@ -24,13 +24,7 @@
* from tail to head, useful for ZREVRANGE. */
zskiplistNode
*
zslCreateNode
(
int
level
,
double
score
,
robj
*
obj
)
{
zskiplistNode
*
zn
=
zmalloc
(
sizeof
(
*
zn
));
zn
->
forward
=
zmalloc
(
sizeof
(
zskiplistNode
*
)
*
level
);
if
(
level
>
1
)
zn
->
span
=
zmalloc
(
sizeof
(
unsigned
int
)
*
(
level
-
1
));
else
zn
->
span
=
NULL
;
zskiplistNode
*
zn
=
zmalloc
(
sizeof
(
*
zn
)
+
level
*
sizeof
(
struct
zskiplistLevel
));
zn
->
score
=
score
;
zn
->
obj
=
obj
;
return
zn
;
...
...
@@ -45,11 +39,8 @@ zskiplist *zslCreate(void) {
zsl
->
length
=
0
;
zsl
->
header
=
zslCreateNode
(
ZSKIPLIST_MAXLEVEL
,
0
,
NULL
);
for
(
j
=
0
;
j
<
ZSKIPLIST_MAXLEVEL
;
j
++
)
{
zsl
->
header
->
forward
[
j
]
=
NULL
;
/* span has space for ZSKIPLIST_MAXLEVEL-1 elements */
if
(
j
<
ZSKIPLIST_MAXLEVEL
-
1
)
zsl
->
header
->
span
[
j
]
=
0
;
zsl
->
header
->
level
[
j
].
forward
=
NULL
;
zsl
->
header
->
level
[
j
].
span
=
0
;
}
zsl
->
header
->
backward
=
NULL
;
zsl
->
tail
=
NULL
;
...
...
@@ -58,19 +49,15 @@ zskiplist *zslCreate(void) {
void
zslFreeNode
(
zskiplistNode
*
node
)
{
decrRefCount
(
node
->
obj
);
zfree
(
node
->
forward
);
zfree
(
node
->
span
);
zfree
(
node
);
}
void
zslFree
(
zskiplist
*
zsl
)
{
zskiplistNode
*
node
=
zsl
->
header
->
forward
[
0
]
,
*
next
;
zskiplistNode
*
node
=
zsl
->
header
->
level
[
0
].
forward
,
*
next
;
zfree
(
zsl
->
header
->
forward
);
zfree
(
zsl
->
header
->
span
);
zfree
(
zsl
->
header
);
while
(
node
)
{
next
=
node
->
forward
[
0
]
;
next
=
node
->
level
[
0
].
forward
;
zslFreeNode
(
node
);
node
=
next
;
}
...
...
@@ -84,7 +71,7 @@ int zslRandomLevel(void) {
return
(
level
<
ZSKIPLIST_MAXLEVEL
)
?
level
:
ZSKIPLIST_MAXLEVEL
;
}
void
zslInsert
(
zskiplist
*
zsl
,
double
score
,
robj
*
obj
)
{
zskiplistNode
*
zslInsert
(
zskiplist
*
zsl
,
double
score
,
robj
*
obj
)
{
zskiplistNode
*
update
[
ZSKIPLIST_MAXLEVEL
],
*
x
;
unsigned
int
rank
[
ZSKIPLIST_MAXLEVEL
];
int
i
,
level
;
...
...
@@ -93,13 +80,12 @@ void zslInsert(zskiplist *zsl, double score, robj *obj) {
for
(
i
=
zsl
->
level
-
1
;
i
>=
0
;
i
--
)
{
/* store rank that is crossed to reach the insert position */
rank
[
i
]
=
i
==
(
zsl
->
level
-
1
)
?
0
:
rank
[
i
+
1
];
while
(
x
->
forward
[
i
]
&&
(
x
->
forward
[
i
]
->
score
<
score
||
(
x
->
forward
[
i
]
->
score
==
score
&&
compareStringObjects
(
x
->
forward
[
i
]
->
obj
,
obj
)
<
0
)))
{
rank
[
i
]
+=
i
>
0
?
x
->
span
[
i
-
1
]
:
1
;
x
=
x
->
forward
[
i
];
while
(
x
->
level
[
i
].
forward
&&
(
x
->
level
[
i
].
forward
->
score
<
score
||
(
x
->
level
[
i
].
forward
->
score
==
score
&&
compareStringObjects
(
x
->
level
[
i
].
forward
->
obj
,
obj
)
<
0
)))
{
rank
[
i
]
+=
x
->
level
[
i
].
span
;
x
=
x
->
level
[
i
].
forward
;
}
update
[
i
]
=
x
;
}
...
...
@@ -112,56 +98,51 @@ void zslInsert(zskiplist *zsl, double score, robj *obj) {
for
(
i
=
zsl
->
level
;
i
<
level
;
i
++
)
{
rank
[
i
]
=
0
;
update
[
i
]
=
zsl
->
header
;
update
[
i
]
->
span
[
i
-
1
]
=
zsl
->
length
;
update
[
i
]
->
level
[
i
].
span
=
zsl
->
length
;
}
zsl
->
level
=
level
;
}
x
=
zslCreateNode
(
level
,
score
,
obj
);
for
(
i
=
0
;
i
<
level
;
i
++
)
{
x
->
forward
[
i
]
=
update
[
i
]
->
forward
[
i
]
;
update
[
i
]
->
forward
[
i
]
=
x
;
x
->
level
[
i
].
forward
=
update
[
i
]
->
level
[
i
].
forward
;
update
[
i
]
->
level
[
i
].
forward
=
x
;
/* update span covered by update[i] as x is inserted here */
if
(
i
>
0
)
{
x
->
span
[
i
-
1
]
=
update
[
i
]
->
span
[
i
-
1
]
-
(
rank
[
0
]
-
rank
[
i
]);
update
[
i
]
->
span
[
i
-
1
]
=
(
rank
[
0
]
-
rank
[
i
])
+
1
;
}
x
->
level
[
i
].
span
=
update
[
i
]
->
level
[
i
].
span
-
(
rank
[
0
]
-
rank
[
i
]);
update
[
i
]
->
level
[
i
].
span
=
(
rank
[
0
]
-
rank
[
i
])
+
1
;
}
/* increment span for untouched levels */
for
(
i
=
level
;
i
<
zsl
->
level
;
i
++
)
{
update
[
i
]
->
span
[
i
-
1
]
++
;
update
[
i
]
->
level
[
i
].
span
++
;
}
x
->
backward
=
(
update
[
0
]
==
zsl
->
header
)
?
NULL
:
update
[
0
];
if
(
x
->
forward
[
0
]
)
x
->
forward
[
0
]
->
backward
=
x
;
if
(
x
->
level
[
0
].
forward
)
x
->
level
[
0
].
forward
->
backward
=
x
;
else
zsl
->
tail
=
x
;
zsl
->
length
++
;
return
x
;
}
/* Internal function used by zslDelete, zslDeleteByScore and zslDeleteByRank */
void
zslDeleteNode
(
zskiplist
*
zsl
,
zskiplistNode
*
x
,
zskiplistNode
**
update
)
{
int
i
;
for
(
i
=
0
;
i
<
zsl
->
level
;
i
++
)
{
if
(
update
[
i
]
->
forward
[
i
]
==
x
)
{
if
(
i
>
0
)
{
update
[
i
]
->
span
[
i
-
1
]
+=
x
->
span
[
i
-
1
]
-
1
;
}
update
[
i
]
->
forward
[
i
]
=
x
->
forward
[
i
];
if
(
update
[
i
]
->
level
[
i
].
forward
==
x
)
{
update
[
i
]
->
level
[
i
].
span
+=
x
->
level
[
i
].
span
-
1
;
update
[
i
]
->
level
[
i
].
forward
=
x
->
level
[
i
].
forward
;
}
else
{
/* invariant: i > 0, because update[0]->forward[0]
* is always equal to x */
update
[
i
]
->
span
[
i
-
1
]
-=
1
;
update
[
i
]
->
level
[
i
].
span
-=
1
;
}
}
if
(
x
->
forward
[
0
]
)
{
x
->
forward
[
0
]
->
backward
=
x
->
backward
;
if
(
x
->
level
[
0
].
forward
)
{
x
->
level
[
0
].
forward
->
backward
=
x
->
backward
;
}
else
{
zsl
->
tail
=
x
->
backward
;
}
while
(
zsl
->
level
>
1
&&
zsl
->
header
->
forward
[
zsl
->
level
-
1
]
==
NULL
)
while
(
zsl
->
level
>
1
&&
zsl
->
header
->
level
[
zsl
->
level
-
1
]
.
forward
==
NULL
)
zsl
->
level
--
;
zsl
->
length
--
;
}
...
...
@@ -173,16 +154,16 @@ int zslDelete(zskiplist *zsl, double score, robj *obj) {
x
=
zsl
->
header
;
for
(
i
=
zsl
->
level
-
1
;
i
>=
0
;
i
--
)
{
while
(
x
->
forward
[
i
]
&&
(
x
->
forward
[
i
]
->
score
<
score
||
(
x
->
forward
[
i
]
->
score
==
score
&&
compareStringObjects
(
x
->
forward
[
i
]
->
obj
,
obj
)
<
0
)))
x
=
x
->
forward
[
i
]
;
while
(
x
->
level
[
i
].
forward
&&
(
x
->
level
[
i
].
forward
->
score
<
score
||
(
x
->
level
[
i
].
forward
->
score
==
score
&&
compareStringObjects
(
x
->
level
[
i
].
forward
->
obj
,
obj
)
<
0
)))
x
=
x
->
level
[
i
].
forward
;
update
[
i
]
=
x
;
}
/* We may have multiple elements with the same score, what we need
* is to find the element with both the right score and object. */
x
=
x
->
forward
[
0
]
;
x
=
x
->
level
[
0
].
forward
;
if
(
x
&&
score
==
x
->
score
&&
equalStringObjects
(
x
->
obj
,
obj
))
{
zslDeleteNode
(
zsl
,
x
,
update
);
zslFreeNode
(
x
);
...
...
@@ -204,16 +185,16 @@ unsigned long zslDeleteRangeByScore(zskiplist *zsl, double min, double max, dict
x
=
zsl
->
header
;
for
(
i
=
zsl
->
level
-
1
;
i
>=
0
;
i
--
)
{
while
(
x
->
forward
[
i
]
&&
x
->
forward
[
i
]
->
score
<
min
)
x
=
x
->
forward
[
i
]
;
while
(
x
->
level
[
i
].
forward
&&
x
->
level
[
i
].
forward
->
score
<
min
)
x
=
x
->
level
[
i
].
forward
;
update
[
i
]
=
x
;
}
/* We may have multiple elements with the same score, what we need
* is to find the element with both the right score and object. */
x
=
x
->
forward
[
0
]
;
x
=
x
->
level
[
0
].
forward
;
while
(
x
&&
x
->
score
<=
max
)
{
zskiplistNode
*
next
=
x
->
forward
[
0
]
;
zslDeleteNode
(
zsl
,
x
,
update
);
zskiplistNode
*
next
=
x
->
level
[
0
].
forward
;
zslDeleteNode
(
zsl
,
x
,
update
);
dictDelete
(
dict
,
x
->
obj
);
zslFreeNode
(
x
);
removed
++
;
...
...
@@ -231,18 +212,18 @@ unsigned long zslDeleteRangeByRank(zskiplist *zsl, unsigned int start, unsigned
x
=
zsl
->
header
;
for
(
i
=
zsl
->
level
-
1
;
i
>=
0
;
i
--
)
{
while
(
x
->
forward
[
i
]
&&
(
traversed
+
(
i
>
0
?
x
->
span
[
i
-
1
]
:
1
)
)
<
start
)
{
traversed
+=
i
>
0
?
x
->
span
[
i
-
1
]
:
1
;
x
=
x
->
forward
[
i
]
;
while
(
x
->
level
[
i
].
forward
&&
(
traversed
+
x
->
level
[
i
].
span
)
<
start
)
{
traversed
+=
x
->
level
[
i
].
span
;
x
=
x
->
level
[
i
].
forward
;
}
update
[
i
]
=
x
;
}
traversed
++
;
x
=
x
->
forward
[
0
]
;
x
=
x
->
level
[
0
].
forward
;
while
(
x
&&
traversed
<=
end
)
{
zskiplistNode
*
next
=
x
->
forward
[
0
]
;
zslDeleteNode
(
zsl
,
x
,
update
);
zskiplistNode
*
next
=
x
->
level
[
0
].
forward
;
zslDeleteNode
(
zsl
,
x
,
update
);
dictDelete
(
dict
,
x
->
obj
);
zslFreeNode
(
x
);
removed
++
;
...
...
@@ -260,12 +241,12 @@ zskiplistNode *zslFirstWithScore(zskiplist *zsl, double score) {
x
=
zsl
->
header
;
for
(
i
=
zsl
->
level
-
1
;
i
>=
0
;
i
--
)
{
while
(
x
->
forward
[
i
]
&&
x
->
forward
[
i
]
->
score
<
score
)
x
=
x
->
forward
[
i
]
;
while
(
x
->
level
[
i
].
forward
&&
x
->
level
[
i
].
forward
->
score
<
score
)
x
=
x
->
level
[
i
].
forward
;
}
/* We may have multiple elements with the same score, what we need
* is to find the element with both the right score and object. */
return
x
->
forward
[
0
]
;
return
x
->
level
[
0
].
forward
;
}
/* Find the rank for an element by both score and key.
...
...
@@ -279,12 +260,12 @@ unsigned long zslistTypeGetRank(zskiplist *zsl, double score, robj *o) {
x
=
zsl
->
header
;
for
(
i
=
zsl
->
level
-
1
;
i
>=
0
;
i
--
)
{
while
(
x
->
forward
[
i
]
&&
(
x
->
forward
[
i
]
->
score
<
score
||
(
x
->
forward
[
i
]
->
score
==
score
&&
compareStringObjects
(
x
->
forward
[
i
]
->
obj
,
o
)
<=
0
)))
{
rank
+=
i
>
0
?
x
->
span
[
i
-
1
]
:
1
;
x
=
x
->
forward
[
i
]
;
while
(
x
->
level
[
i
].
forward
&&
(
x
->
level
[
i
].
forward
->
score
<
score
||
(
x
->
level
[
i
].
forward
->
score
==
score
&&
compareStringObjects
(
x
->
level
[
i
].
forward
->
obj
,
o
)
<=
0
)))
{
rank
+=
x
->
level
[
i
].
span
;
x
=
x
->
level
[
i
].
forward
;
}
/* x might be equal to zsl->header, so test if obj is non-NULL */
...
...
@@ -303,10 +284,10 @@ zskiplistNode* zslistTypeGetElementByRank(zskiplist *zsl, unsigned long rank) {
x
=
zsl
->
header
;
for
(
i
=
zsl
->
level
-
1
;
i
>=
0
;
i
--
)
{
while
(
x
->
forward
[
i
]
&&
(
traversed
+
(
i
>
0
?
x
->
span
[
i
-
1
]
:
1
)
)
<=
rank
)
while
(
x
->
level
[
i
].
forward
&&
(
traversed
+
x
->
level
[
i
].
span
)
<=
rank
)
{
traversed
+=
i
>
0
?
x
->
span
[
i
-
1
]
:
1
;
x
=
x
->
forward
[
i
]
;
traversed
+=
x
->
level
[
i
].
span
;
x
=
x
->
level
[
i
].
forward
;
}
if
(
traversed
==
rank
)
{
return
x
;
...
...
@@ -319,13 +300,11 @@ zskiplistNode* zslistTypeGetElementByRank(zskiplist *zsl, unsigned long rank) {
* Sorted set commands
*----------------------------------------------------------------------------*/
/* This generic command implements both ZADD and ZINCRBY.
* scoreval is the score if the operation is a ZADD (doincrement == 0) or
* the increment if the operation is a ZINCRBY (doincrement == 1). */
void
zaddGenericCommand
(
redisClient
*
c
,
robj
*
key
,
robj
*
ele
,
double
scoreval
,
int
doincrement
)
{
/* This generic command implements both ZADD and ZINCRBY. */
void
zaddGenericCommand
(
redisClient
*
c
,
robj
*
key
,
robj
*
ele
,
double
score
,
int
incr
)
{
robj
*
zsetobj
;
zset
*
zs
;
double
*
scor
e
;
zskiplistNode
*
znod
e
;
zsetobj
=
lookupKeyWrite
(
c
->
db
,
key
);
if
(
zsetobj
==
NULL
)
{
...
...
@@ -339,72 +318,72 @@ void zaddGenericCommand(redisClient *c, robj *key, robj *ele, double scoreval, i
}
zs
=
zsetobj
->
ptr
;
/* Ok now since we implement both ZADD and ZINCRBY here the code
* needs to handle the two different conditions. It's all about setting
* '*score', that is, the new score to set, to the right value. */
score
=
zmalloc
(
sizeof
(
double
));
if
(
doincrement
)
{
dictEntry
*
de
;
/* 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 */
de
=
dictFind
(
zs
->
dict
,
ele
);
if
(
de
)
{
double
*
oldscore
=
dictGetEntryVal
(
de
);
*
score
=
*
oldscore
+
scoreval
;
}
else
{
*
score
=
scoreval
;
}
if
(
isnan
(
*
score
))
{
addReplySds
(
c
,
sdsnew
(
"-ERR resulting score is not a number (NaN)
\r\n
"
));
zfree
(
score
);
dictEntry
*
de
=
dictFind
(
zs
->
dict
,
ele
);
if
(
de
!=
NULL
)
score
+=
*
(
double
*
)
dictGetEntryVal
(
de
);
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. */
return
;
}
}
else
{
*
score
=
scoreval
;
}
/* What follows is a simple remove and re-insert operation that is common
* to both ZADD and ZINCRBY... */
if
(
dictAdd
(
zs
->
dict
,
ele
,
score
)
==
DICT_OK
)
{
/* case 1: New element */
/* 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
;
/* New element */
incrRefCount
(
ele
);
/* added to hash */
zslInsert
(
zs
->
zsl
,
*
score
,
ele
);
znode
=
zslInsert
(
zs
->
zsl
,
score
,
ele
);
incrRefCount
(
ele
);
/* added to skiplist */
/* Update the score in the dict entry */
de
=
dictFind
(
zs
->
dict
,
ele
);
redisAssert
(
de
!=
NULL
);
dictGetEntryVal
(
de
)
=
&
znode
->
score
;
touchWatchedKey
(
c
->
db
,
c
->
argv
[
1
]);
server
.
dirty
++
;
if
(
do
incr
ement
)
addReplyDouble
(
c
,
*
score
);
if
(
incr
)
addReplyDouble
(
c
,
score
);
else
addReply
(
c
,
shared
.
cone
);
}
else
{
dictEntry
*
de
;
double
*
oldscore
;
robj
*
curobj
;
double
*
curscore
;
int
deleted
;
/*
case 2: Score update operation
*/
/*
Update score
*/
de
=
dictFind
(
zs
->
dict
,
ele
);
redisAssert
(
de
!=
NULL
);
oldscore
=
dictGetEntryVal
(
de
);
if
(
*
score
!=
*
oldscore
)
{
int
deleted
;
curobj
=
dictGetEntryKey
(
de
);
curscore
=
dictGetEntryVal
(
de
);
/* Remove and insert the element in the skip list with new score */
deleted
=
zslDelete
(
zs
->
zsl
,
*
oldscore
,
ele
);
/* 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
);
zslInsert
(
zs
->
zsl
,
*
score
,
ele
);
incrRefCount
(
ele
);
/* Update the score in the hash table */
dictReplace
(
zs
->
dict
,
ele
,
score
);
znode
=
zslInsert
(
zs
->
zsl
,
score
,
curobj
);
incrRefCount
(
curobj
);
/* Update the score in the current dict entry */
dictGetEntryVal
(
de
)
=
&
znode
->
score
;
touchWatchedKey
(
c
->
db
,
c
->
argv
[
1
]);
server
.
dirty
++
;
}
else
{
zfree
(
score
);
}
if
(
do
incr
ement
)
addReplyDouble
(
c
,
*
score
);
if
(
incr
)
addReplyDouble
(
c
,
score
);
else
addReply
(
c
,
shared
.
czero
);
}
...
...
@@ -426,7 +405,7 @@ void zremCommand(redisClient *c) {
robj
*
zsetobj
;
zset
*
zs
;
dictEntry
*
de
;
double
*
old
score
;
double
cur
score
;
int
deleted
;
if
((
zsetobj
=
lookupKeyWriteOrReply
(
c
,
c
->
argv
[
1
],
shared
.
czero
))
==
NULL
||
...
...
@@ -439,8 +418,8 @@ void zremCommand(redisClient *c) {
return
;
}
/* Delete from the skiplist */
old
score
=
dictGetEntryVal
(
de
);
deleted
=
zslDelete
(
zs
->
zsl
,
*
old
score
,
c
->
argv
[
2
]);
cur
score
=
*
(
double
*
)
dictGetEntryVal
(
de
);
deleted
=
zslDelete
(
zs
->
zsl
,
cur
score
,
c
->
argv
[
2
]);
redisAssert
(
deleted
!=
0
);
/* Delete from the hash table */
...
...
@@ -554,6 +533,7 @@ void zunionInterGenericCommand(redisClient *c, robj *dstkey, int op) {
zsetopsrc
*
src
;
robj
*
dstobj
;
zset
*
dstzset
;
zskiplistNode
*
znode
;
dictIterator
*
di
;
dictEntry
*
de
;
int
touched
=
0
;
...
...
@@ -561,7 +541,8 @@ void zunionInterGenericCommand(redisClient *c, robj *dstkey, int op) {
/* expect setnum input keys to be given */
setnum
=
atoi
(
c
->
argv
[
2
]
->
ptr
);
if
(
setnum
<
1
)
{
addReplySds
(
c
,
sdsnew
(
"-ERR at least 1 input key is needed for ZUNIONSTORE/ZINTERSTORE
\r\n
"
));
addReplyError
(
c
,
"at least 1 input key is needed for ZUNIONSTORE/ZINTERSTORE"
);
return
;
}
...
...
@@ -644,28 +625,26 @@ void zunionInterGenericCommand(redisClient *c, robj *dstkey, int op) {
* from small to large, all src[i > 0].dict are non-empty too */
di
=
dictGetIterator
(
src
[
0
].
dict
);
while
((
de
=
dictNext
(
di
))
!=
NULL
)
{
double
*
score
=
zmalloc
(
sizeof
(
double
)),
value
;
*
score
=
src
[
0
].
weight
*
zunionInterDictValue
(
de
);
double
score
,
value
;
score
=
src
[
0
].
weight
*
zunionInterDictValue
(
de
);
for
(
j
=
1
;
j
<
setnum
;
j
++
)
{
dictEntry
*
other
=
dictFind
(
src
[
j
].
dict
,
dictGetEntryKey
(
de
));
if
(
other
)
{
value
=
src
[
j
].
weight
*
zunionInterDictValue
(
other
);
zunionInterAggregate
(
score
,
value
,
aggregate
);
zunionInterAggregate
(
&
score
,
value
,
aggregate
);
}
else
{
break
;
}
}
/* skip entry when not present in every source dict */
if
(
j
!=
setnum
)
{
zfree
(
score
);
}
else
{
/* accept entry only when present in every source dict */
if
(
j
==
setnum
)
{
robj
*
o
=
dictGetEntryKey
(
de
);
dictAdd
(
dstzset
->
dict
,
o
,
score
);
incrRefCount
(
o
);
/* added to dictionary */
zslInsert
(
dstzset
->
zsl
,
*
score
,
o
);
znode
=
zslInsert
(
dstzset
->
zsl
,
score
,
o
);
incrRefCount
(
o
);
/* added to skiplist */
dictAdd
(
dstzset
->
dict
,
o
,
&
znode
->
score
);
incrRefCount
(
o
);
/* added to dictionary */
}
}
dictReleaseIterator
(
di
);
...
...
@@ -676,11 +655,12 @@ void zunionInterGenericCommand(redisClient *c, robj *dstkey, int op) {
di
=
dictGetIterator
(
src
[
i
].
dict
);
while
((
de
=
dictNext
(
di
))
!=
NULL
)
{
/* skip key when already processed */
if
(
dictFind
(
dstzset
->
dict
,
dictGetEntryKey
(
de
))
!=
NULL
)
continue
;
double
score
,
value
;
double
*
score
=
zmalloc
(
sizeof
(
double
)),
value
;
*
score
=
src
[
i
].
weight
*
zunionInterDictValue
(
de
);
/* skip key when already processed */
if
(
dictFind
(
dstzset
->
dict
,
dictGetEntryKey
(
de
))
!=
NULL
)
continue
;
score
=
src
[
i
].
weight
*
zunionInterDictValue
(
de
);
/* because the zsets are sorted by size, its only possible
* for sets at larger indices to hold this entry */
...
...
@@ -688,15 +668,15 @@ void zunionInterGenericCommand(redisClient *c, robj *dstkey, int op) {
dictEntry
*
other
=
dictFind
(
src
[
j
].
dict
,
dictGetEntryKey
(
de
));
if
(
other
)
{
value
=
src
[
j
].
weight
*
zunionInterDictValue
(
other
);
zunionInterAggregate
(
score
,
value
,
aggregate
);
zunionInterAggregate
(
&
score
,
value
,
aggregate
);
}
}
robj
*
o
=
dictGetEntryKey
(
de
);
dictAdd
(
dstzset
->
dict
,
o
,
score
);
incrRefCount
(
o
);
/* added to dictionary */
zslInsert
(
dstzset
->
zsl
,
*
score
,
o
);
znode
=
zslInsert
(
dstzset
->
zsl
,
score
,
o
);
incrRefCount
(
o
);
/* added to skiplist */
dictAdd
(
dstzset
->
dict
,
o
,
&
znode
->
score
);
incrRefCount
(
o
);
/* added to dictionary */
}
dictReleaseIterator
(
di
);
}
...
...
@@ -778,18 +758,17 @@ void zrangeGenericCommand(redisClient *c, int reverse) {
ln
=
start
==
0
?
zsl
->
tail
:
zslistTypeGetElementByRank
(
zsl
,
llen
-
start
);
}
else
{
ln
=
start
==
0
?
zsl
->
header
->
forward
[
0
]
:
zslistTypeGetElementByRank
(
zsl
,
start
+
1
);
zsl
->
header
->
level
[
0
].
forward
:
zslistTypeGetElementByRank
(
zsl
,
start
+
1
);
}
/* Return the result in form of a multi-bulk reply */
addReplySds
(
c
,
sdscatprintf
(
sdsempty
(),
"*%d
\r\n
"
,
withscores
?
(
rangelen
*
2
)
:
rangelen
));
addReplyMultiBulkLen
(
c
,
withscores
?
(
rangelen
*
2
)
:
rangelen
);
for
(
j
=
0
;
j
<
rangelen
;
j
++
)
{
ele
=
ln
->
obj
;
addReplyBulk
(
c
,
ele
);
if
(
withscores
)
addReplyDouble
(
c
,
ln
->
score
);
ln
=
reverse
?
ln
->
backward
:
ln
->
forward
[
0
]
;
ln
=
reverse
?
ln
->
backward
:
ln
->
level
[
0
].
forward
;
}
}
...
...
@@ -840,8 +819,7 @@ void genericZrangebyscoreCommand(redisClient *c, int justcount) {
if
(
c
->
argc
!=
(
4
+
withscores
)
&&
c
->
argc
!=
(
7
+
withscores
))
badsyntax
=
1
;
if
(
badsyntax
)
{
addReplySds
(
c
,
sdsnew
(
"-ERR wrong number of arguments for ZRANGEBYSCORE
\r\n
"
));
addReplyError
(
c
,
"wrong number of arguments for ZRANGEBYSCORE"
);
return
;
}
...
...
@@ -866,13 +844,14 @@ void genericZrangebyscoreCommand(redisClient *c, int justcount) {
zset
*
zsetobj
=
o
->
ptr
;
zskiplist
*
zsl
=
zsetobj
->
zsl
;
zskiplistNode
*
ln
;
robj
*
ele
,
*
lenobj
=
NULL
;
robj
*
ele
;
void
*
replylen
=
NULL
;
unsigned
long
rangelen
=
0
;
/* Get the first node with the score >= min, or with
* score > min if 'minex' is true. */
ln
=
zslFirstWithScore
(
zsl
,
min
);
while
(
minex
&&
ln
&&
ln
->
score
==
min
)
ln
=
ln
->
forward
[
0
]
;
while
(
minex
&&
ln
&&
ln
->
score
==
min
)
ln
=
ln
->
level
[
0
].
forward
;
if
(
ln
==
NULL
)
{
/* No element matching the speciifed interval */
...
...
@@ -884,16 +863,13 @@ void genericZrangebyscoreCommand(redisClient *c, int justcount) {
* are in the list, so we push this object that will represent
* the multi-bulk length in the output buffer, and will "fix"
* it later */
if
(
!
justcount
)
{
lenobj
=
createObject
(
REDIS_STRING
,
NULL
);
addReply
(
c
,
lenobj
);
decrRefCount
(
lenobj
);
}
if
(
!
justcount
)
replylen
=
addDeferredMultiBulkLength
(
c
);
while
(
ln
&&
(
maxex
?
(
ln
->
score
<
max
)
:
(
ln
->
score
<=
max
)))
{
if
(
offset
)
{
offset
--
;
ln
=
ln
->
forward
[
0
]
;
ln
=
ln
->
level
[
0
].
forward
;
continue
;
}
if
(
limit
==
0
)
break
;
...
...
@@ -903,14 +879,14 @@ void genericZrangebyscoreCommand(redisClient *c, int justcount) {
if
(
withscores
)
addReplyDouble
(
c
,
ln
->
score
);
}
ln
=
ln
->
forward
[
0
]
;
ln
=
ln
->
level
[
0
].
forward
;
rangelen
++
;
if
(
limit
>
0
)
limit
--
;
}
if
(
justcount
)
{
addReplyLongLong
(
c
,(
long
)
rangelen
);
}
else
{
lenobj
->
ptr
=
sdscatprintf
(
sdsempty
(),
"*%lu
\r\n
"
,
setDeferredMultiBulkLength
(
c
,
replylen
,
withscores
?
(
rangelen
*
2
)
:
rangelen
);
}
}
...
...
@@ -933,7 +909,7 @@ void zcardCommand(redisClient *c) {
checkType
(
c
,
o
,
REDIS_ZSET
))
return
;
zs
=
o
->
ptr
;
addReply
Ul
ong
(
c
,
zs
->
zsl
->
length
);
addReply
LongL
ong
(
c
,
zs
->
zsl
->
length
);
}
void
zscoreCommand
(
redisClient
*
c
)
{
...
...
src/testhelp.h
0 → 100644
View file @
b04ce2a3
/* This is a really minimal testing framework for C.
*
* Example:
*
* test_cond("Check if 1 == 1", 1==1)
* test_cond("Check if 5 > 10", 5 > 10)
* test_report()
*
* Copyright (c) 2010, 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.
*/
#ifndef __TESTHELP_H
#define __TESTHELP_H
int
__failed_tests
=
0
;
int
__test_num
=
0
;
#define test_cond(descr,_c) do { \
__test_num++; printf("%d - %s: ", __test_num, descr); \
if(_c) printf("PASSED\n"); else {printf("FAILED\n"); __failed_tests++;} \
} while(0);
#define test_report() do { \
printf("%d tests, %d passed, %d failed\n", __test_num, \
__test_num-__failed_tests, __failed_tests); \
if (__failed_tests) { \
printf("=== WARNING === We have failed tests here...\n"); \
} \
} while(0);
#endif
src/util.c
View file @
b04ce2a3
...
...
@@ -200,24 +200,44 @@ int ll2string(char *s, size_t len, long long value) {
return
l
;
}
/* Check if the
nul-terminated
string 's' can be represented by a long
/* Check if the
sds
string 's' can be represented by a long
long
* (that is, is a number that fits into long without any other space or
* character before or after the digits).
* character before or after the digits, so that converting this number
* back to a string will result in the same bytes as the original string).
*
* If so, the function returns REDIS_OK and *longval is set to the value
* If so, the function returns REDIS_OK and *
l
longval is set to the value
* of the number. Otherwise REDIS_ERR is returned */
int
isStringRepresentableAsLong
(
sds
s
,
long
*
longval
)
{
int
isStringRepresentableAsLong
Long
(
sds
s
,
long
long
*
l
longval
)
{
char
buf
[
32
],
*
endptr
;
long
value
;
long
long
value
;
int
slen
;
value
=
strtol
(
s
,
&
endptr
,
10
);
value
=
strtol
l
(
s
,
&
endptr
,
10
);
if
(
endptr
[
0
]
!=
'\0'
)
return
REDIS_ERR
;
slen
=
ll2string
(
buf
,
32
,
value
);
/* If the number converted back into a string is not identical
* then it's not possible to encode the string as integer */
if
(
sdslen
(
s
)
!=
(
unsigned
)
slen
||
memcmp
(
buf
,
s
,
slen
))
return
REDIS_ERR
;
if
(
longval
)
*
longval
=
value
;
if
(
llongval
)
*
llongval
=
value
;
return
REDIS_OK
;
}
int
isStringRepresentableAsLong
(
sds
s
,
long
*
longval
)
{
long
long
ll
;
if
(
isStringRepresentableAsLongLong
(
s
,
&
ll
)
==
REDIS_ERR
)
return
REDIS_ERR
;
if
(
ll
<
LONG_MIN
||
ll
>
LONG_MAX
)
return
REDIS_ERR
;
*
longval
=
(
long
)
ll
;
return
REDIS_OK
;
}
int
isObjectRepresentableAsLongLong
(
robj
*
o
,
long
long
*
llongval
)
{
redisAssert
(
o
->
type
==
REDIS_STRING
);
if
(
o
->
encoding
==
REDIS_ENCODING_INT
)
{
if
(
llongval
)
*
llongval
=
(
long
)
o
->
ptr
;
return
REDIS_OK
;
}
else
{
return
isStringRepresentableAsLongLong
(
o
->
ptr
,
llongval
);
}
}
src/version.h
View file @
b04ce2a3
#define REDIS_VERSION "2.1.
2
"
#define REDIS_VERSION "2.1.
4
"
src/vm.c
View file @
b04ce2a3
...
...
@@ -110,6 +110,11 @@ void vmInit(void) {
/* LZF requires a lot of stack */
pthread_attr_init
(
&
server
.
io_threads_attr
);
pthread_attr_getstacksize
(
&
server
.
io_threads_attr
,
&
stacksize
);
/* Solaris may report a stacksize of 0, let's set it to 1 otherwise
* multiplying it by 2 in the while loop later will not really help ;) */
if
(
!
stacksize
)
stacksize
=
1
;
while
(
stacksize
<
REDIS_THREAD_STACK_SIZE
)
stacksize
*=
2
;
pthread_attr_setstacksize
(
&
server
.
io_threads_attr
,
stacksize
);
/* Listen for events in the threaded I/O pipe */
...
...
@@ -395,6 +400,10 @@ double computeObjectSwappability(robj *o) {
z
=
(
o
->
type
==
REDIS_ZSET
);
d
=
z
?
((
zset
*
)
o
->
ptr
)
->
dict
:
o
->
ptr
;
if
(
!
z
&&
o
->
encoding
==
REDIS_ENCODING_INTSET
)
{
intset
*
is
=
o
->
ptr
;
asize
=
sizeof
(
*
is
)
+
is
->
encoding
*
is
->
length
;
}
else
{
asize
=
sizeof
(
dict
)
+
(
sizeof
(
struct
dictEntry
*
)
*
dictSlots
(
d
));
if
(
z
)
asize
+=
sizeof
(
zset
)
-
sizeof
(
dict
);
if
(
dictSize
(
d
))
{
...
...
@@ -405,6 +414,7 @@ double computeObjectSwappability(robj *o) {
asize
+=
(
sizeof
(
struct
dictEntry
)
+
elesize
)
*
dictSize
(
d
);
if
(
z
)
asize
+=
sizeof
(
zskiplistNode
)
*
dictSize
(
d
);
}
}
break
;
case
REDIS_HASH
:
if
(
o
->
encoding
==
REDIS_ENCODING_ZIPMAP
)
{
...
...
@@ -543,7 +553,15 @@ void freeIOJob(iojob *j) {
/* Every time a thread finished a Job, it writes a byte into the write side
* of an unix pipe in order to "awake" the main thread, and this function
* is called. */
* is called.
*
* Note that this is called both by the event loop, when a I/O thread
* sends a byte in the notification pipe, and is also directly called from
* waitEmptyIOJobsQueue().
*
* In the latter case we don't want to swap more, so we use the
* "privdata" argument setting it to a not NULL value to signal this
* condition. */
void
vmThreadedIOCompletedJob
(
aeEventLoop
*
el
,
int
fd
,
void
*
privdata
,
int
mask
)
{
...
...
@@ -553,6 +571,8 @@ void vmThreadedIOCompletedJob(aeEventLoop *el, int fd, void *privdata,
REDIS_NOTUSED
(
mask
);
REDIS_NOTUSED
(
privdata
);
if
(
privdata
!=
NULL
)
trytoswap
=
0
;
/* check the comments above... */
/* For every byte we read in the read side of the pipe, there is one
* I/O job completed to process. */
while
((
retval
=
read
(
fd
,
buf
,
1
))
==
1
)
{
...
...
@@ -864,7 +884,8 @@ void waitEmptyIOJobsQueue(void) {
io_processed_len
=
listLength
(
server
.
io_processed
);
unlockThreadedIO
();
if
(
io_processed_len
)
{
vmThreadedIOCompletedJob
(
NULL
,
server
.
io_ready_pipe_read
,
NULL
,
0
);
vmThreadedIOCompletedJob
(
NULL
,
server
.
io_ready_pipe_read
,
(
void
*
)
0xdeadbeef
,
0
);
usleep
(
1000
);
/* 1 millisecond */
}
else
{
usleep
(
10000
);
/* 10 milliseconds */
...
...
src/ziplist.c
View file @
b04ce2a3
/* Memory layout of a ziplist, containing "foo", "bar", "quux":
* <zlbytes><zllen><len>"foo"<len>"bar"<len>"quux"
/* The ziplist is a specially encoded dually linked list that is designed
* to be very memory efficient. It stores both strings and integer values,
* where integers are encoded as actual integers instead of a series of
* characters. It allows push and pop operations on either side of the list
* in O(1) time. However, because every operation requires a reallocation of
* the memory used by the ziplist, the actual complexity is related to the
* amount of memory used by the ziplist.
*
* <zlbytes> is an unsigned integer to hold the number of bytes that
* the ziplist occupies. This is stored to not have to traverse the ziplist
* to know the new length when pushing.
* ----------------------------------------------------------------------------
*
*
<zllen> is the number of items in the ziplist. When this value is
*
greater than 254, we need to traverse the entire list to know
*
how many items it holds.
*
ZIPLIST OVERALL LAYOUT:
*
The general layout of the ziplist is as follows:
*
<zlbytes><zltail><zllen><entry><entry><zlend>
*
* <len> is the number of bytes occupied by a single entry. When this
* number is greater than 253, the length will occupy 5 bytes, where
* the extra bytes contain an unsigned integer to hold the length.
* <zlbytes> is an unsigned integer to hold the number of bytes that the
* ziplist occupies. This value needs to be stored to be able to resize the
* entire structure without the need to traverse it first.
*
* <zltail> is the offset to the last entry in the list. This allows a pop
* operation on the far side of the list without the need for full traversal.
*
* <zllen> is the number of entries.When this value is larger than 2**16-2,
* we need to traverse the entire list to know how many items it holds.
*
* <zlend> is a single byte special value, equal to 255, which indicates the
* end of the list.
*
* ZIPLIST ENTRIES:
* Every entry in the ziplist is prefixed by a header that contains two pieces
* of information. First, the length of the previous entry is stored to be
* able to traverse the list from back to front. Second, the encoding with an
* optional string length of the entry itself is stored.
*
* The length of the previous entry is encoded in the following way:
* If this length is smaller than 254 bytes, it will only consume a single
* byte that takes the length as value. When the length is greater than or
* equal to 254, it will consume 5 bytes. The first byte is set to 254 to
* indicate a larger value is following. The remaining 4 bytes take the
* length of the previous entry as value.
*
* The other header field of the entry itself depends on the contents of the
* entry. When the entry is a string, the first 2 bits of this header will hold
* the type of encoding used to store the length of the string, followed by the
* actual length of the string. When the entry is an integer the first 2 bits
* are both set to 1. The following 2 bits are used to specify what kind of
* integer will be stored after this header. An overview of the different
* types and encodings is as follows:
*
* |00pppppp| - 1 byte
* String value with length less than or equal to 63 bytes (6 bits).
* |01pppppp|qqqqqqqq| - 2 bytes
* String value with length less than or equal to 16383 bytes (14 bits).
* |10______|qqqqqqqq|rrrrrrrr|ssssssss|tttttttt| - 5 bytes
* String value with length greater than or equal to 16384 bytes.
* |1100____| - 1 byte
* Integer encoded as int16_t (2 bytes).
* |1101____| - 1 byte
* Integer encoded as int32_t (4 bytes).
* |1110____| - 1 byte
* Integer encoded as int64_t (8 bytes).
*/
#include <stdio.h>
...
...
@@ -25,25 +71,20 @@
int
ll2string
(
char
*
s
,
size_t
len
,
long
long
value
);
/* Important note: the ZIP_END value is used to depict the end of the
* ziplist structure. When a pointer contains an entry, the first couple
* of bytes contain the encoded length of the previous entry. This length
* is encoded as ZIP_ENC_RAW length, so the first two bits will contain 00
* and the byte will therefore never have a value of 255. */
#define ZIP_END 255
#define ZIP_BIGLEN 254
/* Entry encoding */
#define ZIP_ENC_RAW 0
#define ZIP_ENC_INT16 1
#define ZIP_ENC_INT32 2
#define ZIP_ENC_INT64 3
#define ZIP_ENCODING(p) ((p)[0] >> 6)
/* Different encoding/length possibilities */
#define ZIP_STR_06B (0 << 6)
#define ZIP_STR_14B (1 << 6)
#define ZIP_STR_32B (2 << 6)
#define ZIP_INT_16B (0xc0 | 0<<4)
#define ZIP_INT_32B (0xc0 | 1<<4)
#define ZIP_INT_64B (0xc0 | 2<<4)
/* Length encoding for raw entries */
#define ZIP_LEN_INLINE 0
#define ZIP_LEN_UINT16 1
#define ZIP_LEN_UINT32 2
/* Macro's to determine type */
#define ZIP_IS_STR(enc) (((enc) & 0xc0) < 0xc0)
#define ZIP_IS_INT(enc) (!ZIP_IS_STR(enc) && ((enc) & 0x30) < 0x30)
/* Utility macros */
#define ZIPLIST_BYTES(zl) (*((uint32_t*)(zl)))
...
...
@@ -67,14 +108,25 @@ typedef struct zlentry {
unsigned
char
*
p
;
}
zlentry
;
/* Return the encoding pointer to by 'p'. */
static
unsigned
int
zipEntryEncoding
(
unsigned
char
*
p
)
{
/* String encoding: 2 MSBs */
unsigned
char
b
=
p
[
0
]
&
0xc0
;
if
(
b
<
0xc0
)
{
return
b
;
}
else
{
/* Integer encoding: 4 MSBs */
return
p
[
0
]
&
0xf0
;
}
assert
(
NULL
);
}
/* Return bytes needed to store integer encoded by 'encoding' */
static
unsigned
int
zipEncodingSize
(
unsigned
char
encoding
)
{
if
(
encoding
==
ZIP_ENC_INT16
)
{
return
sizeof
(
int16_t
);
}
else
if
(
encoding
==
ZIP_ENC_INT32
)
{
return
sizeof
(
int32_t
);
}
else
if
(
encoding
==
ZIP_ENC_INT64
)
{
return
sizeof
(
int64_t
);
static
unsigned
int
zipIntSize
(
unsigned
char
encoding
)
{
switch
(
encoding
)
{
case
ZIP_INT_16B
:
return
sizeof
(
int16_t
);
case
ZIP_INT_32B
:
return
sizeof
(
int32_t
);
case
ZIP_INT_64B
:
return
sizeof
(
int64_t
);
}
assert
(
NULL
);
}
...
...
@@ -82,23 +134,28 @@ static unsigned int zipEncodingSize(unsigned char encoding) {
/* Decode the encoded length pointed by 'p'. If a pointer to 'lensize' is
* provided, it is set to the number of bytes required to encode the length. */
static
unsigned
int
zipDecodeLength
(
unsigned
char
*
p
,
unsigned
int
*
lensize
)
{
unsigned
char
encoding
=
ZIP_ENCODING
(
p
),
lenenc
;
unsigned
char
encoding
=
zipEntryEncoding
(
p
)
;
unsigned
int
len
;
if
(
encoding
==
ZIP_ENC_RAW
)
{
lenenc
=
(
p
[
0
]
>>
4
)
&
0x3
;
if
(
lenenc
==
ZIP_LEN_INLINE
)
{
len
=
p
[
0
]
&
0xf
;
if
(
ZIP_IS_STR
(
encoding
)
)
{
switch
(
encoding
)
{
case
ZIP_STR_06B
:
len
=
p
[
0
]
&
0x
3
f
;
if
(
lensize
)
*
lensize
=
1
;
}
else
if
(
lenenc
==
ZIP_LEN_UINT16
)
{
len
=
p
[
1
]
|
(
p
[
2
]
<<
8
);
if
(
lensize
)
*
lensize
=
3
;
}
else
{
len
=
p
[
1
]
|
(
p
[
2
]
<<
8
)
|
(
p
[
3
]
<<
16
)
|
(
p
[
4
]
<<
24
);
break
;
case
ZIP_STR_14B
:
len
=
((
p
[
0
]
&
0x3f
)
<<
8
)
|
p
[
1
];
if
(
lensize
)
*
lensize
=
2
;
break
;
case
ZIP_STR_32B
:
len
=
(
p
[
1
]
<<
24
)
|
(
p
[
2
]
<<
16
)
|
(
p
[
3
]
<<
8
)
|
p
[
4
];
if
(
lensize
)
*
lensize
=
5
;
break
;
default:
assert
(
NULL
);
}
}
else
{
len
=
zip
Encoding
Size
(
encoding
);
len
=
zip
Int
Size
(
encoding
);
if
(
lensize
)
*
lensize
=
1
;
}
return
len
;
...
...
@@ -106,34 +163,36 @@ static unsigned int zipDecodeLength(unsigned char *p, unsigned int *lensize) {
/* Encode the length 'l' writing it in 'p'. If p is NULL it just returns
* the amount of bytes required to encode such a length. */
static
unsigned
int
zipEncodeLength
(
unsigned
char
*
p
,
char
encoding
,
unsigned
int
rawlen
)
{
unsigned
char
len
=
1
,
lenenc
,
buf
[
5
];
if
(
encoding
==
ZIP_ENC_RAW
)
{
if
(
rawlen
<=
0xf
)
{
static
unsigned
int
zipEncodeLength
(
unsigned
char
*
p
,
unsigned
char
encoding
,
unsigned
int
rawlen
)
{
unsigned
char
len
=
1
,
buf
[
5
];
if
(
ZIP_IS_STR
(
encoding
))
{
/* Although encoding is given it may not be set for strings,
* so we determine it here using the raw length. */
if
(
rawlen
<=
0x3f
)
{
if
(
!
p
)
return
len
;
lenenc
=
ZIP_LEN_INLINE
;
buf
[
0
]
=
rawlen
;
}
else
if
(
rawlen
<=
0xffff
)
{
len
+=
2
;
buf
[
0
]
=
ZIP_STR_06B
|
rawlen
;
}
else
if
(
rawlen
<=
0x3fff
)
{
len
+=
1
;
if
(
!
p
)
return
len
;
lenenc
=
ZIP_LEN_UINT16
;
buf
[
1
]
=
(
rawlen
)
&
0xff
;
buf
[
2
]
=
(
rawlen
>>
8
)
&
0xff
;
buf
[
0
]
=
ZIP_STR_14B
|
((
rawlen
>>
8
)
&
0x3f
);
buf
[
1
]
=
rawlen
&
0xff
;
}
else
{
len
+=
4
;
if
(
!
p
)
return
len
;
lenenc
=
ZIP_LEN_UINT32
;
buf
[
1
]
=
(
rawlen
)
&
0xff
;
buf
[
2
]
=
(
rawlen
>>
8
)
&
0xff
;
buf
[
3
]
=
(
rawlen
>>
16
)
&
0xff
;
buf
[
4
]
=
(
rawlen
>>
24
)
&
0xff
;
}
buf
[
0
]
=
(
lenenc
<<
4
)
|
(
buf
[
0
]
&
0xf
);
buf
[
0
]
=
ZIP_STR_32B
;
buf
[
1
]
=
(
rawlen
>>
24
)
&
0xff
;
buf
[
2
]
=
(
rawlen
>>
16
)
&
0xff
;
buf
[
3
]
=
(
rawlen
>>
8
)
&
0xff
;
buf
[
4
]
=
rawlen
&
0xff
;
}
}
else
{
/* Implies integer encoding, so length is always 1. */
if
(
!
p
)
return
len
;
buf
[
0
]
=
encoding
;
}
/* Apparently we need to store the length in 'p' */
buf
[
0
]
=
(
encoding
<<
6
)
|
(
buf
[
0
]
&
0x3f
);
/* Store this length at p */
memcpy
(
p
,
buf
,
len
);
return
len
;
}
...
...
@@ -167,6 +226,14 @@ static unsigned int zipPrevEncodeLength(unsigned char *p, unsigned int len) {
}
}
/* Encode the length of the previous entry and write it to "p". This only
* uses the larger encoding (required in __ziplistCascadeUpdate). */
static
void
zipPrevEncodeLengthForceLarge
(
unsigned
char
*
p
,
unsigned
int
len
)
{
if
(
p
==
NULL
)
return
;
p
[
0
]
=
ZIP_BIGLEN
;
memcpy
(
p
+
1
,
&
len
,
sizeof
(
len
));
}
/* Return the difference in number of bytes needed to store the new length
* "len" on the entry pointed to by "p". */
static
int
zipPrevLenByteDiff
(
unsigned
char
*
p
,
unsigned
int
len
)
{
...
...
@@ -198,11 +265,11 @@ static int zipTryEncoding(unsigned char *entry, unsigned int entrylen, long long
/* Great, the string can be encoded. Check what's the smallest
* of our encoding types that can hold this value. */
if
(
value
>=
INT16_MIN
&&
value
<=
INT16_MAX
)
{
*
encoding
=
ZIP_
ENC_
INT16
;
*
encoding
=
ZIP_INT
_
16
B
;
}
else
if
(
value
>=
INT32_MIN
&&
value
<=
INT32_MAX
)
{
*
encoding
=
ZIP_
ENC_
INT32
;
*
encoding
=
ZIP_INT
_
32
B
;
}
else
{
*
encoding
=
ZIP_
ENC_
INT64
;
*
encoding
=
ZIP_INT
_
64
B
;
}
*
v
=
value
;
return
1
;
...
...
@@ -215,13 +282,13 @@ static void zipSaveInteger(unsigned char *p, int64_t value, unsigned char encodi
int16_t
i16
;
int32_t
i32
;
int64_t
i64
;
if
(
encoding
==
ZIP_
ENC_
INT16
)
{
if
(
encoding
==
ZIP_INT
_
16
B
)
{
i16
=
value
;
memcpy
(
p
,
&
i16
,
sizeof
(
i16
));
}
else
if
(
encoding
==
ZIP_
ENC_
INT32
)
{
}
else
if
(
encoding
==
ZIP_INT
_
32
B
)
{
i32
=
value
;
memcpy
(
p
,
&
i32
,
sizeof
(
i32
));
}
else
if
(
encoding
==
ZIP_
ENC_
INT64
)
{
}
else
if
(
encoding
==
ZIP_INT
_
64
B
)
{
i64
=
value
;
memcpy
(
p
,
&
i64
,
sizeof
(
i64
));
}
else
{
...
...
@@ -234,13 +301,13 @@ static int64_t zipLoadInteger(unsigned char *p, unsigned char encoding) {
int16_t
i16
;
int32_t
i32
;
int64_t
i64
,
ret
;
if
(
encoding
==
ZIP_
ENC_
INT16
)
{
if
(
encoding
==
ZIP_INT
_
16
B
)
{
memcpy
(
&
i16
,
p
,
sizeof
(
i16
));
ret
=
i16
;
}
else
if
(
encoding
==
ZIP_
ENC_
INT32
)
{
}
else
if
(
encoding
==
ZIP_INT
_
32
B
)
{
memcpy
(
&
i32
,
p
,
sizeof
(
i32
));
ret
=
i32
;
}
else
if
(
encoding
==
ZIP_
ENC_
INT64
)
{
}
else
if
(
encoding
==
ZIP_INT
_
64
B
)
{
memcpy
(
&
i64
,
p
,
sizeof
(
i64
));
ret
=
i64
;
}
else
{
...
...
@@ -255,7 +322,7 @@ static zlentry zipEntry(unsigned char *p) {
e
.
prevrawlen
=
zipPrevDecodeLength
(
p
,
&
e
.
prevrawlensize
);
e
.
len
=
zipDecodeLength
(
p
+
e
.
prevrawlensize
,
&
e
.
lensize
);
e
.
headersize
=
e
.
prevrawlensize
+
e
.
lensize
;
e
.
encoding
=
ZIP_ENCODING
(
p
+
e
.
prevrawlensize
);
e
.
encoding
=
zipEntryEncoding
(
p
+
e
.
prevrawlensize
);
e
.
p
=
p
;
return
e
;
}
...
...
@@ -285,11 +352,86 @@ static unsigned char *ziplistResize(unsigned char *zl, unsigned int len) {
return
zl
;
}
/* When an entry is inserted, we need to set the prevlen field of the next
* entry to equal the length of the inserted entry. It can occur that this
* length cannot be encoded in 1 byte and the next entry needs to be grow
* a bit larger to hold the 5-byte encoded prevlen. This can be done for free,
* because this only happens when an entry is already being inserted (which
* causes a realloc and memmove). However, encoding the prevlen may require
* that this entry is grown as well. This effect may cascade throughout
* the ziplist when there are consecutive entries with a size close to
* ZIP_BIGLEN, so we need to check that the prevlen can be encoded in every
* consecutive entry.
*
* Note that this effect can also happen in reverse, where the bytes required
* to encode the prevlen field can shrink. This effect is deliberately ignored,
* because it can cause a "flapping" effect where a chain prevlen fields is
* first grown and then shrunk again after consecutive inserts. Rather, the
* field is allowed to stay larger than necessary, because a large prevlen
* field implies the ziplist is holding large entries anyway.
*
* The pointer "p" points to the first entry that does NOT need to be
* updated, i.e. consecutive fields MAY need an update. */
static
unsigned
char
*
__ziplistCascadeUpdate
(
unsigned
char
*
zl
,
unsigned
char
*
p
)
{
unsigned
int
curlen
=
ZIPLIST_BYTES
(
zl
),
rawlen
,
rawlensize
;
unsigned
int
offset
,
noffset
,
extra
;
unsigned
char
*
np
;
zlentry
cur
,
next
;
while
(
p
[
0
]
!=
ZIP_END
)
{
cur
=
zipEntry
(
p
);
rawlen
=
cur
.
headersize
+
cur
.
len
;
rawlensize
=
zipPrevEncodeLength
(
NULL
,
rawlen
);
/* Abort if there is no next entry. */
if
(
p
[
rawlen
]
==
ZIP_END
)
break
;
next
=
zipEntry
(
p
+
rawlen
);
/* Abort when "prevlen" has not changed. */
if
(
next
.
prevrawlen
==
rawlen
)
break
;
if
(
next
.
prevrawlensize
<
rawlensize
)
{
/* The "prevlen" field of "next" needs more bytes to hold
* the raw length of "cur". */
offset
=
p
-
zl
;
extra
=
rawlensize
-
next
.
prevrawlensize
;
zl
=
ziplistResize
(
zl
,
curlen
+
extra
);
ZIPLIST_TAIL_OFFSET
(
zl
)
+=
extra
;
p
=
zl
+
offset
;
/* Move the tail to the back. */
np
=
p
+
rawlen
;
noffset
=
np
-
zl
;
memmove
(
np
+
rawlensize
,
np
+
next
.
prevrawlensize
,
curlen
-
noffset
-
next
.
prevrawlensize
-
1
);
zipPrevEncodeLength
(
np
,
rawlen
);
/* Advance the cursor */
p
+=
rawlen
;
}
else
{
if
(
next
.
prevrawlensize
>
rawlensize
)
{
/* This would result in shrinking, which we want to avoid.
* So, set "rawlen" in the available bytes. */
zipPrevEncodeLengthForceLarge
(
p
+
rawlen
,
rawlen
);
}
else
{
zipPrevEncodeLength
(
p
+
rawlen
,
rawlen
);
}
/* Stop here, as the raw length of "next" has not changed. */
break
;
}
}
return
zl
;
}
/* Delete "num" entries, starting at "p". Returns pointer to the ziplist. */
static
unsigned
char
*
__ziplistDelete
(
unsigned
char
*
zl
,
unsigned
char
*
p
,
unsigned
int
num
)
{
unsigned
int
i
,
totlen
,
deleted
=
0
;
int
nextdiff
=
0
;
zlentry
first
=
zipEntry
(
p
);
int
offset
,
nextdiff
=
0
;
zlentry
first
,
tail
;
first
=
zipEntry
(
p
);
for
(
i
=
0
;
p
[
0
]
!=
ZIP_END
&&
i
<
num
;
i
++
)
{
p
+=
zipRawEntryLength
(
p
);
deleted
++
;
...
...
@@ -306,7 +448,14 @@ static unsigned char *__ziplistDelete(unsigned char *zl, unsigned char *p, unsig
zipPrevEncodeLength
(
p
-
nextdiff
,
first
.
prevrawlen
);
/* Update offset for tail */
ZIPLIST_TAIL_OFFSET
(
zl
)
-=
totlen
+
nextdiff
;
ZIPLIST_TAIL_OFFSET
(
zl
)
-=
totlen
;
/* When the tail contains more than one entry, we need to take
* "nextdiff" in account as well. Otherwise, a change in the
* size of prevlen doesn't have an effect on the *tail* offset. */
tail
=
zipEntry
(
p
);
if
(
p
[
tail
.
headersize
+
tail
.
len
]
!=
ZIP_END
)
ZIPLIST_TAIL_OFFSET
(
zl
)
+=
nextdiff
;
/* Move tail to the front of the ziplist */
memmove
(
first
.
p
,
p
-
nextdiff
,
ZIPLIST_BYTES
(
zl
)
-
(
p
-
zl
)
-
1
+
nextdiff
);
...
...
@@ -316,8 +465,15 @@ static unsigned char *__ziplistDelete(unsigned char *zl, unsigned char *p, unsig
}
/* Resize and update length */
offset
=
first
.
p
-
zl
;
zl
=
ziplistResize
(
zl
,
ZIPLIST_BYTES
(
zl
)
-
totlen
+
nextdiff
);
ZIPLIST_INCR_LENGTH
(
zl
,
-
deleted
);
p
=
zl
+
offset
;
/* When nextdiff != 0, the raw length of the next entry has changed, so
* we need to cascade the update throughout the ziplist */
if
(
nextdiff
!=
0
)
zl
=
__ziplistCascadeUpdate
(
zl
,
p
);
}
return
zl
;
}
...
...
@@ -326,29 +482,30 @@ static unsigned char *__ziplistDelete(unsigned char *zl, unsigned char *p, unsig
static
unsigned
char
*
__ziplistInsert
(
unsigned
char
*
zl
,
unsigned
char
*
p
,
unsigned
char
*
s
,
unsigned
int
slen
)
{
unsigned
int
curlen
=
ZIPLIST_BYTES
(
zl
),
reqlen
,
prevlen
=
0
;
unsigned
int
offset
,
nextdiff
=
0
;
unsigned
char
*
tail
;
unsigned
char
encoding
=
ZIP_ENC_RAW
;
unsigned
char
encoding
=
0
;
long
long
value
;
zlentry
entry
;
zlentry
entry
,
tail
;
/* Find out prevlen for the entry that is inserted. */
if
(
p
[
0
]
!=
ZIP_END
)
{
entry
=
zipEntry
(
p
);
prevlen
=
entry
.
prevrawlen
;
}
else
{
tail
=
ZIPLIST_ENTRY_TAIL
(
zl
);
if
(
tail
[
0
]
!=
ZIP_END
)
{
prevlen
=
zipRawEntryLength
(
tail
);
unsigned
char
*
p
tail
=
ZIPLIST_ENTRY_TAIL
(
zl
);
if
(
p
tail
[
0
]
!=
ZIP_END
)
{
prevlen
=
zipRawEntryLength
(
p
tail
);
}
}
/* See if the entry can be encoded */
if
(
zipTryEncoding
(
s
,
slen
,
&
value
,
&
encoding
))
{
reqlen
=
zipEncodingSize
(
encoding
);
/* 'encoding' is set to the appropriate integer encoding */
reqlen
=
zipIntSize
(
encoding
);
}
else
{
/* 'encoding' is untouched, however zipEncodeLength will use the
* string length to figure out how to encode it. */
reqlen
=
slen
;
}
/* We need space for both the length of the previous entry and
* the length of the payload. */
reqlen
+=
zipPrevEncodeLength
(
NULL
,
prevlen
);
...
...
@@ -368,22 +525,39 @@ static unsigned char *__ziplistInsert(unsigned char *zl, unsigned char *p, unsig
if
(
p
[
0
]
!=
ZIP_END
)
{
/* Subtract one because of the ZIP_END bytes */
memmove
(
p
+
reqlen
,
p
-
nextdiff
,
curlen
-
offset
-
1
+
nextdiff
);
/* Encode this entry's raw length in the next entry. */
zipPrevEncodeLength
(
p
+
reqlen
,
reqlen
);
/* Update offset for tail */
ZIPLIST_TAIL_OFFSET
(
zl
)
+=
reqlen
+
nextdiff
;
ZIPLIST_TAIL_OFFSET
(
zl
)
+=
reqlen
;
/* When the tail contains more than one entry, we need to take
* "nextdiff" in account as well. Otherwise, a change in the
* size of prevlen doesn't have an effect on the *tail* offset. */
tail
=
zipEntry
(
p
+
reqlen
);
if
(
p
[
reqlen
+
tail
.
headersize
+
tail
.
len
]
!=
ZIP_END
)
ZIPLIST_TAIL_OFFSET
(
zl
)
+=
nextdiff
;
}
else
{
/* This element will be the new tail. */
ZIPLIST_TAIL_OFFSET
(
zl
)
=
p
-
zl
;
}
/* When nextdiff != 0, the raw length of the next entry has changed, so
* we need to cascade the update throughout the ziplist */
if
(
nextdiff
!=
0
)
{
offset
=
p
-
zl
;
zl
=
__ziplistCascadeUpdate
(
zl
,
p
+
reqlen
);
p
=
zl
+
offset
;
}
/* Write the entry */
p
+=
zipPrevEncodeLength
(
p
,
prevlen
);
p
+=
zipEncodeLength
(
p
,
encoding
,
slen
);
if
(
encoding
!=
ZIP_ENC_RAW
)
{
zipSaveInteger
(
p
,
value
,
encoding
);
}
else
{
if
(
ZIP_IS_STR
(
encoding
))
{
memcpy
(
p
,
s
,
slen
);
}
else
{
zipSaveInteger
(
p
,
value
,
encoding
);
}
ZIPLIST_INCR_LENGTH
(
zl
,
1
);
return
zl
;
...
...
@@ -449,6 +623,7 @@ unsigned char *ziplistPrev(unsigned char *zl, unsigned char *p) {
return
NULL
;
}
else
{
entry
=
zipEntry
(
p
);
assert
(
entry
.
prevrawlen
>
0
);
return
p
-
entry
.
prevrawlen
;
}
}
...
...
@@ -463,7 +638,7 @@ unsigned int ziplistGet(unsigned char *p, unsigned char **sstr, unsigned int *sl
if
(
sstr
)
*
sstr
=
NULL
;
entry
=
zipEntry
(
p
);
if
(
entry
.
encoding
==
ZIP_ENC_RAW
)
{
if
(
ZIP_IS_STR
(
entry
.
encoding
)
)
{
if
(
sstr
)
{
*
slen
=
entry
.
len
;
*
sstr
=
p
+
entry
.
headersize
;
...
...
@@ -510,7 +685,7 @@ unsigned int ziplistCompare(unsigned char *p, unsigned char *sstr, unsigned int
if
(
p
[
0
]
==
ZIP_END
)
return
0
;
entry
=
zipEntry
(
p
);
if
(
entry
.
encoding
==
ZIP_ENC_RAW
)
{
if
(
ZIP_IS_STR
(
entry
.
encoding
)
)
{
/* Raw compare */
if
(
entry
.
len
==
slen
)
{
return
memcmp
(
p
+
entry
.
headersize
,
sstr
,
slen
)
==
0
;
...
...
@@ -554,21 +729,52 @@ unsigned int ziplistSize(unsigned char *zl) {
void
ziplistRepr
(
unsigned
char
*
zl
)
{
unsigned
char
*
p
;
int
index
=
0
;
zlentry
entry
;
printf
(
"{total bytes %d} {length %u}
\n
"
,
ZIPLIST_BYTES
(
zl
),
ZIPLIST_LENGTH
(
zl
));
printf
(
"{total bytes %d} "
"{length %u}
\n
"
"{tail offset %u}
\n
"
,
ZIPLIST_BYTES
(
zl
),
ZIPLIST_LENGTH
(
zl
),
ZIPLIST_TAIL_OFFSET
(
zl
));
p
=
ZIPLIST_ENTRY_HEAD
(
zl
);
while
(
*
p
!=
ZIP_END
)
{
entry
=
zipEntry
(
p
);
printf
(
"{offset %ld, header %u, payload %u} "
,
p
-
zl
,
entry
.
headersize
,
entry
.
len
);
printf
(
"{"
"addr 0x%08lx, "
"index %2d, "
"offset %5ld, "
"rl: %5u, "
"hs %2u, "
"pl: %5u, "
"pls: %2u, "
"payload %5u"
"} "
,
(
long
unsigned
int
)
p
,
index
,
p
-
zl
,
entry
.
headersize
+
entry
.
len
,
entry
.
headersize
,
entry
.
prevrawlen
,
entry
.
prevrawlensize
,
entry
.
len
);
p
+=
entry
.
headersize
;
if
(
entry
.
encoding
==
ZIP_ENC_RAW
)
{
if
(
ZIP_IS_STR
(
entry
.
encoding
))
{
if
(
entry
.
len
>
40
)
{
fwrite
(
p
,
40
,
1
,
stdout
);
printf
(
"..."
);
}
else
{
fwrite
(
p
,
entry
.
len
,
1
,
stdout
);
}
}
else
{
printf
(
"%lld"
,
(
long
long
)
zipLoadInteger
(
p
,
entry
.
encoding
));
}
printf
(
"
\n
"
);
p
+=
entry
.
len
;
index
++
;
}
printf
(
"{end}
\n\n
"
);
}
...
...
@@ -664,6 +870,10 @@ int main(int argc, char **argv) {
unsigned
int
elen
;
long
long
value
;
/* If an argument is given, use it as the random seed. */
if
(
argc
==
2
)
srand
(
atoi
(
argv
[
1
]));
zl
=
createIntList
();
ziplistRepr
(
zl
);
...
...
@@ -915,6 +1125,25 @@ int main(int argc, char **argv) {
ziplistRepr
(
zl
);
}
printf
(
"Regression test for >255 byte strings:
\n
"
);
{
char
v1
[
257
],
v2
[
257
];
memset
(
v1
,
'x'
,
256
);
memset
(
v2
,
'y'
,
256
);
zl
=
ziplistNew
();
zl
=
ziplistPush
(
zl
,(
unsigned
char
*
)
v1
,
strlen
(
v1
),
ZIPLIST_TAIL
);
zl
=
ziplistPush
(
zl
,(
unsigned
char
*
)
v2
,
strlen
(
v2
),
ZIPLIST_TAIL
);
/* Pop values again and compare their value. */
p
=
ziplistIndex
(
zl
,
0
);
assert
(
ziplistGet
(
p
,
&
entry
,
&
elen
,
&
value
));
assert
(
strncmp
(
v1
,
entry
,
elen
)
==
0
);
p
=
ziplistIndex
(
zl
,
1
);
assert
(
ziplistGet
(
p
,
&
entry
,
&
elen
,
&
value
));
assert
(
strncmp
(
v2
,
entry
,
elen
)
==
0
);
printf
(
"SUCCESS
\n\n
"
);
}
printf
(
"Create long list and check indices:
\n
"
);
{
zl
=
ziplistNew
();
...
...
@@ -958,7 +1187,57 @@ int main(int argc, char **argv) {
printf
(
"ERROR:
\"
1025
\"\n
"
);
return
1
;
}
printf
(
"SUCCESS
\n
"
);
printf
(
"SUCCESS
\n\n
"
);
}
printf
(
"Stress with random payloads of different encoding:
\n
"
);
{
int
i
,
idx
,
where
,
len
;
long
long
v
;
unsigned
char
*
p
;
char
buf
[
0x4041
];
/* max length of generated string */
zl
=
ziplistNew
();
for
(
i
=
0
;
i
<
100000
;
i
++
)
{
where
=
(
rand
()
&
1
)
?
ZIPLIST_HEAD
:
ZIPLIST_TAIL
;
if
(
rand
()
&
1
)
{
/* equally likely create a 16, 32 or 64 bit int */
v
=
(
rand
()
&
INT16_MAX
)
+
((
1ll
<<
32
)
>>
((
rand
()
%
3
)
*
16
));
v
*=
2
*
(
rand
()
&
1
)
-
1
;
/* randomly flip sign */
sprintf
(
buf
,
"%lld"
,
v
);
zl
=
ziplistPush
(
zl
,
(
unsigned
char
*
)
buf
,
strlen
(
buf
),
where
);
}
else
{
/* equally likely generate 6, 14 or >14 bit length */
v
=
rand
()
&
0x3f
;
v
+=
0x4000
>>
((
rand
()
%
3
)
*
8
);
memset
(
buf
,
'x'
,
v
);
zl
=
ziplistPush
(
zl
,
(
unsigned
char
*
)
buf
,
v
,
where
);
}
/* delete a random element */
if
((
len
=
ziplistLen
(
zl
))
>=
10
)
{
idx
=
rand
()
%
len
;
// printf("Delete index %d\n", idx);
// ziplistRepr(zl);
ziplistDeleteRange
(
zl
,
idx
,
1
);
// ziplistRepr(zl);
len
--
;
}
/* iterate from front to back */
idx
=
0
;
p
=
ziplistIndex
(
zl
,
0
);
while
((
p
=
ziplistNext
(
zl
,
p
)))
idx
++
;
assert
(
len
==
idx
+
1
);
/* iterate from back to front */
idx
=
0
;
p
=
ziplistIndex
(
zl
,
-
1
);
while
((
p
=
ziplistPrev
(
zl
,
p
)))
idx
++
;
assert
(
len
==
idx
+
1
);
}
printf
(
"SUCCESS
\n\n
"
);
}
printf
(
"Stress with variable ziplist size:
\n
"
);
...
...
src/zmalloc.c
View file @
b04ce2a3
...
...
@@ -32,6 +32,7 @@
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include "config.h"
#if defined(__sun)
...
...
@@ -170,3 +171,69 @@ size_t zmalloc_used_memory(void) {
void
zmalloc_enable_thread_safeness
(
void
)
{
zmalloc_thread_safe
=
1
;
}
/* Fragmentation = RSS / allocated-bytes */
#if defined(HAVE_PROCFS)
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
float
zmalloc_get_fragmentation_ratio
(
void
)
{
size_t
allocated
=
zmalloc_used_memory
();
int
page
=
sysconf
(
_SC_PAGESIZE
);
size_t
rss
;
char
buf
[
4096
];
char
filename
[
256
];
int
fd
,
count
;
char
*
p
,
*
x
;
snprintf
(
filename
,
256
,
"/proc/%d/stat"
,
getpid
());
if
((
fd
=
open
(
filename
,
O_RDONLY
))
==
-
1
)
return
0
;
if
(
read
(
fd
,
buf
,
4096
)
<=
0
)
{
close
(
fd
);
return
0
;
}
close
(
fd
);
p
=
buf
;
count
=
23
;
/* RSS is the 24th field in /proc/<pid>/stat */
while
(
p
&&
count
--
)
{
p
=
strchr
(
p
,
' '
);
if
(
p
)
p
++
;
}
if
(
!
p
)
return
0
;
x
=
strchr
(
p
,
' '
);
if
(
!
x
)
return
0
;
*
x
=
'\0'
;
rss
=
strtoll
(
p
,
NULL
,
10
);
rss
*=
page
;
return
(
float
)
rss
/
allocated
;
}
#elif defined(HAVE_TASKINFO)
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/sysctl.h>
#include <mach/task.h>
#include <mach/mach_init.h>
float
zmalloc_get_fragmentation_ratio
(
void
)
{
task_t
task
=
MACH_PORT_NULL
;
struct
task_basic_info
t_info
;
mach_msg_type_number_t
t_info_count
=
TASK_BASIC_INFO_COUNT
;
if
(
task_for_pid
(
current_task
(),
getpid
(),
&
task
)
!=
KERN_SUCCESS
)
return
0
;
task_info
(
task
,
TASK_BASIC_INFO
,
(
task_info_t
)
&
t_info
,
&
t_info_count
);
return
(
float
)
t_info
.
resident_size
/
zmalloc_used_memory
();
}
#else
float
zmalloc_get_fragmentation_ratio
(
void
)
{
return
0
;
}
#endif
src/zmalloc.h
View file @
b04ce2a3
...
...
@@ -38,5 +38,6 @@ void zfree(void *ptr);
char
*
zstrdup
(
const
char
*
s
);
size_t
zmalloc_used_memory
(
void
);
void
zmalloc_enable_thread_safeness
(
void
);
float
zmalloc_get_fragmentation_ratio
(
void
);
#endif
/* _ZMALLOC_H */
tests/integration/redis-cli.tcl
0 → 100644
View file @
b04ce2a3
start_server
{
tags
{
"cli"
}}
{
proc open_cli
{}
{
set ::env
(
TERM
)
dumb
set fd
[
open
[
format
"|src/redis-cli -p %d -n 9"
[
srv port
]]
"r+"
]
fconfigure $fd -buffering none
fconfigure $fd -blocking false
fconfigure $fd -translation binary
assert_equal
"redis> "
[
read_cli $fd
]
set _ $fd
}
proc close_cli
{
fd
}
{
close $fd
}
proc read_cli
{
fd
}
{
set buf
[
read $fd
]
while
{[
string length $buf
]
== 0
}
{
# wait some time and try again
after 10
set buf
[
read $fd
]
}
set _ $buf
}
proc write_cli
{
fd buf
}
{
puts $fd $buf
flush $fd
}
# Helpers to run tests in interactive mode
proc run_command
{
fd cmd
}
{
write_cli $fd $cmd
set lines
[
split
[
read_cli $fd
]
"
\n
"
]
assert_equal
"redis> "
[
lindex $lines end
]
join
[
lrange $lines 0 end-1
]
"
\n
"
}
proc test_interactive_cli
{
name code
}
{
set ::env
(
FAKETTY
)
1
set fd
[
open_cli
]
test
"Interactive CLI:
$name
"
$code
close_cli $fd
unset ::env
(
FAKETTY
)
}
# Helpers to run tests where stdout is not a tty
proc write_tmpfile
{
contents
}
{
set tmp
[
tmpfile
"cli"
]
set tmpfd
[
open $tmp
"w"
]
puts -nonewline $tmpfd $contents
close $tmpfd
set _ $tmp
}
proc _run_cli
{
opts args
}
{
set cmd
[
format
"src/redis-cli -p %d -n 9
$args
"
[
srv port
]]
foreach
{
key value
}
$opts
{
if
{
$key
eq
"pipe"
}
{
set cmd
"sh -c
\"
$value
|
$cmd
\"
"
}
if
{
$key
eq
"path"
}
{
set cmd
"
$cmd
<
$value
"
}
}
set fd
[
open
"|
$cmd
"
"r"
]
fconfigure $fd -buffering none
fconfigure $fd -translation binary
set resp
[
read $fd 1048576
]
close $fd
set _ $resp
}
proc run_cli
{
args
}
{
_run_cli
{}
{*}
$args
}
proc run_cli_with_input_pipe
{
cmd args
}
{
_run_cli
[
list pipe $cmd
]
{*}
$args
}
proc run_cli_with_input_file
{
path args
}
{
_run_cli
[
list path $path
]
{*}
$args
}
proc test_nontty_cli
{
name code
}
{
test
"Non-interactive non-TTY CLI:
$name
"
$code
}
# Helpers to run tests where stdout is a tty
(
fake it
)
proc test_tty_cli
{
name code
}
{
set ::env
(
FAKETTY
)
1
test
"Non-interactive TTY CLI:
$name
"
$code
unset ::env
(
FAKETTY
)
}
test_interactive_cli
"INFO response should be printed raw"
{
set lines
[
split
[
run_command $fd info
]
"
\n
"
]
foreach line $lines
{
assert
[
regexp
{
^
[
a-z0-9_
]
+:
[
a-z0-9_
]
+
}
$line
]
}
}
test_interactive_cli
"Status reply"
{
assert_equal
"OK"
[
run_command $fd
"set key foo"
]
}
test_interactive_cli
"Integer reply"
{
assert_equal
"(integer) 1"
[
run_command $fd
"incr counter"
]
}
test_interactive_cli
"Bulk reply"
{
r set key foo
assert_equal
"
\"
foo
\"
"
[
run_command $fd
"get key"
]
}
test_interactive_cli
"Multi-bulk reply"
{
r rpush list foo
r rpush list bar
assert_equal
"1.
\"
foo
\"\n
2.
\"
bar
\"
"
[
run_command $fd
"lrange list 0 -1"
]
}
test_interactive_cli
"Parsing quotes"
{
assert_equal
"OK"
[
run_command $fd
"set key
\"
bar
\"
"
]
assert_equal
"bar"
[
r get key
]
assert_equal
"OK"
[
run_command $fd
"set key
\"
bar
\"
"
]
assert_equal
" bar "
[
r get key
]
assert_equal
"OK"
[
run_command $fd
"set key
\"\\\"
bar
\\\"\"
"
]
assert_equal
"
\"
bar
\"
"
[
r get key
]
assert_equal
"OK"
[
run_command $fd
"set key
\"\t
bar
\t\"
"
]
assert_equal
"
\t
bar
\t
"
[
r get key
]
# invalid quotation
assert_equal
"Invalid argument(s)"
[
run_command $fd
"get
\"\"
key"
]
assert_equal
"Invalid argument(s)"
[
run_command $fd
"get
\"
key
\"
x"
]
# quotes after the argument are weird, but should be allowed
assert_equal
"OK"
[
run_command $fd
"set key
\"\"
bar"
]
assert_equal
"bar"
[
r get key
]
}
test_tty_cli
"Status reply"
{
assert_equal
"OK
\n
"
[
run_cli set key bar
]
assert_equal
"bar"
[
r get key
]
}
test_tty_cli
"Integer reply"
{
r del counter
assert_equal
"(integer) 1
\n
"
[
run_cli incr counter
]
}
test_tty_cli
"Bulk reply"
{
r set key
"tab
\t
newline
\n
"
assert_equal
"
\"
tab
\\
tnewline
\\
n
\"\n
"
[
run_cli get key
]
}
test_tty_cli
"Multi-bulk reply"
{
r del list
r rpush list foo
r rpush list bar
assert_equal
"1.
\"
foo
\"\n
2.
\"
bar
\"\n
"
[
run_cli lrange list 0 -1
]
}
test_tty_cli
"Read last argument from pipe"
{
assert_equal
"OK
\n
"
[
run_cli_with_input_pipe
"echo foo"
set key
]
assert_equal
"foo
\n
"
[
r get key
]
}
test_tty_cli
"Read last argument from file"
{
set tmpfile
[
write_tmpfile
"from file"
]
assert_equal
"OK
\n
"
[
run_cli_with_input_file $tmpfile set key
]
assert_equal
"from file"
[
r get key
]
}
test_nontty_cli
"Status reply"
{
assert_equal
"OK"
[
run_cli set key bar
]
assert_equal
"bar"
[
r get key
]
}
test_nontty_cli
"Integer reply"
{
r del counter
assert_equal
"1"
[
run_cli incr counter
]
}
test_nontty_cli
"Bulk reply"
{
r set key
"tab
\t
newline
\n
"
assert_equal
"tab
\t
newline
\n
"
[
run_cli get key
]
}
test_nontty_cli
"Multi-bulk reply"
{
r del list
r rpush list foo
r rpush list bar
assert_equal
"foo
\n
bar"
[
run_cli lrange list 0 -1
]
}
test_nontty_cli
"Read last argument from pipe"
{
assert_equal
"OK"
[
run_cli_with_input_pipe
"echo foo"
set key
]
assert_equal
"foo
\n
"
[
r get key
]
}
test_nontty_cli
"Read last argument from file"
{
set tmpfile
[
write_tmpfile
"from file"
]
assert_equal
"OK"
[
run_cli_with_input_file $tmpfile set key
]
assert_equal
"from file"
[
r get key
]
}
}
tests/integration/replication.tcl
View file @
b04ce2a3
...
...
@@ -23,6 +23,24 @@ start_server {tags {"repl"}} {
}
assert_equal
[
r debug digest
]
[
r -1 debug digest
]
}
test
{
MASTER and SLAVE consistency with expire
}
{
createComplexDataset r 50000 useexpire
after 4000
;
# Make sure everything expired before taking the digest
if
{[
r debug digest
]
ne
[
r -1 debug digest
]}
{
set csv1
[
csvdump r
]
set csv2
[
csvdump
{
r -1
}]
set fd
[
open /tmp/repldump1.txt w
]
puts -nonewline $fd $csv1
close $fd
set fd
[
open /tmp/repldump2.txt w
]
puts -nonewline $fd $csv2
close $fd
puts
"Master - Slave inconsistency"
puts
"Run diff -u against /tmp/repldump*.txt for more info"
}
assert_equal
[
r debug digest
]
[
r -1 debug digest
]
}
}
}
...
...
tests/support/server.tcl
View file @
b04ce2a3
...
...
@@ -83,7 +83,9 @@ proc ping_server {host port} {
}
close $fd
}
e
]}
{
puts
"Can't PING server at
$host:$port...
$e
"
puts -nonewline
"."
}
else
{
puts -nonewline
"ok"
}
return $retval
}
...
...
@@ -170,14 +172,33 @@ proc start_server {options {code undefined}} {
if
{
$::valgrind
}
{
exec valgrind src/redis-server $config_file > $stdout 2> $stderr &
after 2000
}
else
{
exec src/redis-server $config_file > $stdout 2> $stderr &
after 500
}
# check that the server actually started
if
{
$code
ne
"undefined"
&& !
[
ping_server $::host $::port
]}
{
# ugly but tries to be as fast as possible...
set retrynum 20
set serverisup 0
puts -nonewline
"=== (
$tags
) Starting server
${::host}
:
${::port}
"
after 10
if
{
$code
ne
"undefined"
}
{
while
{[
incr retrynum -1
]}
{
catch
{
if
{[
ping_server $::host $::port
]}
{
set serverisup 1
}
}
if
{
$serverisup
}
break
after 50
}
}
else
{
set serverisup 1
}
puts
{}
if
{
!$serverisup
}
{
error_and_quit $config_file
[
exec cat $stderr
]
}
...
...
@@ -230,7 +251,11 @@ proc start_server {options {code undefined}} {
# execute provided block
set curnum $::testnum
catch
{
uplevel 1 $code
}
err
if
{
!
[
catch
{
uplevel 1 $code
}
err
]}
{
# zero exit status is good
unset err
}
if
{
$curnum
== $::testnum
}
{
# don't check for leaks when no tests were executed
dict set srv
"skipleaks"
1
...
...
@@ -241,6 +266,7 @@ proc start_server {options {code undefined}} {
# allow an exception to bubble up the call chain but still kill this
# server, because we want to reuse the ports when the tests are re-run
if
{[
info exists err
]}
{
if
{
$err
eq
"exception"
}
{
puts
[
format
"Logged warnings (pid %d):"
[
dict get $srv
"pid"
]]
set warnings
[
warnings_from_file
[
dict get $srv
"stdout"
]]
...
...
@@ -258,6 +284,7 @@ proc start_server {options {code undefined}} {
puts $err
exit 1
}
}
set ::tags
[
lrange $::tags 0 end-
[
llength $tags
]]
kill_server $srv
...
...
tests/support/test.tcl
View file @
b04ce2a3
...
...
@@ -36,8 +36,8 @@ proc assert_encoding {enc key} {
# Swapped out values don't have an encoding, so make sure that
# the value is swapped in before checking the encoding.
set dbg
[
r debug object $key
]
while
{[
string match
"* swapped:*"
$dbg
]}
{
[
r debug swapin $key
]
while
{[
string match
"* swapped
at
:*"
$dbg
]}
{
r debug swapin $key
set dbg
[
r debug object $key
]
}
assert_match
"* encoding:
$enc
*"
$dbg
...
...
Prev
1
2
3
4
Next
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