Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
Menu
Open sidebar
ruanhaishen
redis
Commits
acc75bfd
Commit
acc75bfd
authored
Aug 26, 2010
by
antirez
Browse files
Merge remote branch 'pietern/intset-split'
parents
b4b62c34
f9d5c4e3
Changes
15
Show whitespace changes
Inline
Side-by-side
src/Makefile
View file @
acc75bfd
...
@@ -19,7 +19,7 @@ INSTALL_TOP= /usr/local
...
@@ -19,7 +19,7 @@ INSTALL_TOP= /usr/local
INSTALL_BIN
=
$(INSTALL_TOP)
/bin
INSTALL_BIN
=
$(INSTALL_TOP)
/bin
INSTALL
=
cp
-p
INSTALL
=
cp
-p
OBJ
=
adlist.o ae.o anet.o dict.o redis.o sds.o zmalloc.o lzf_c.o lzf_d.o pqsort.o zipmap.o sha1.o ziplist.o release.o networking.o util.o object.o db.o replication.o rdb.o t_string.o t_list.o t_set.o t_zset.o t_hash.o config.o aof.o vm.o pubsub.o multi.o debug.o sort.o
OBJ
=
adlist.o ae.o anet.o dict.o redis.o sds.o zmalloc.o lzf_c.o lzf_d.o pqsort.o zipmap.o sha1.o ziplist.o release.o networking.o util.o object.o db.o replication.o rdb.o t_string.o t_list.o t_set.o t_zset.o t_hash.o config.o aof.o vm.o pubsub.o multi.o debug.o sort.o
intset.o
BENCHOBJ
=
ae.o anet.o redis-benchmark.o sds.o adlist.o zmalloc.o
BENCHOBJ
=
ae.o anet.o redis-benchmark.o sds.o adlist.o zmalloc.o
CLIOBJ
=
anet.o sds.o adlist.o redis-cli.o zmalloc.o linenoise.o
CLIOBJ
=
anet.o sds.o adlist.o redis-cli.o zmalloc.o linenoise.o
CHECKDUMPOBJ
=
redis-check-dump.o lzf_c.o lzf_d.o
CHECKDUMPOBJ
=
redis-check-dump.o lzf_c.o lzf_d.o
...
@@ -58,6 +58,7 @@ sds.o: sds.c sds.h zmalloc.h
...
@@ -58,6 +58,7 @@ sds.o: sds.c sds.h zmalloc.h
sha1.o
:
sha1.c sha1.h
sha1.o
:
sha1.c sha1.h
ziplist.o
:
ziplist.c zmalloc.h ziplist.h
ziplist.o
:
ziplist.c zmalloc.h ziplist.h
zipmap.o
:
zipmap.c zmalloc.h
zipmap.o
:
zipmap.c zmalloc.h
intset.o
:
intset.c zmalloc.h
zmalloc.o
:
zmalloc.c config.h
zmalloc.o
:
zmalloc.c config.h
redis-server
:
$(OBJ)
redis-server
:
$(OBJ)
...
...
src/aof.c
View file @
acc75bfd
...
@@ -463,20 +463,30 @@ int rewriteAppendOnlyFile(char *filename) {
...
@@ -463,20 +463,30 @@ int rewriteAppendOnlyFile(char *filename) {
redisPanic
(
"Unknown list encoding"
);
redisPanic
(
"Unknown list encoding"
);
}
}
}
else
if
(
o
->
type
==
REDIS_SET
)
{
}
else
if
(
o
->
type
==
REDIS_SET
)
{
char
cmd
[]
=
"*3
\r\n
$4
\r\n
SADD
\r\n
"
;
/* Emit the SADDs needed to rebuild the set */
/* Emit the SADDs needed to rebuild the set */
dict
*
set
=
o
->
ptr
;
if
(
o
->
encoding
==
REDIS_ENCODING_INTSET
)
{
dictIterator
*
di
=
dictGetIterator
(
set
);
int
ii
=
0
;
long
long
llval
;
while
(
intsetGet
(
o
->
ptr
,
ii
++
,
&
llval
))
{
if
(
fwrite
(
cmd
,
sizeof
(
cmd
)
-
1
,
1
,
fp
)
==
0
)
goto
werr
;
if
(
fwriteBulkObject
(
fp
,
&
key
)
==
0
)
goto
werr
;
if
(
fwriteBulkLongLong
(
fp
,
llval
)
==
0
)
goto
werr
;
}
}
else
if
(
o
->
encoding
==
REDIS_ENCODING_HT
)
{
dictIterator
*
di
=
dictGetIterator
(
o
->
ptr
);
dictEntry
*
de
;
dictEntry
*
de
;
while
((
de
=
dictNext
(
di
))
!=
NULL
)
{
while
((
de
=
dictNext
(
di
))
!=
NULL
)
{
char
cmd
[]
=
"*3
\r\n
$4
\r\n
SADD
\r\n
"
;
robj
*
eleobj
=
dictGetEntryKey
(
de
);
robj
*
eleobj
=
dictGetEntryKey
(
de
);
if
(
fwrite
(
cmd
,
sizeof
(
cmd
)
-
1
,
1
,
fp
)
==
0
)
goto
werr
;
if
(
fwrite
(
cmd
,
sizeof
(
cmd
)
-
1
,
1
,
fp
)
==
0
)
goto
werr
;
if
(
fwriteBulkObject
(
fp
,
&
key
)
==
0
)
goto
werr
;
if
(
fwriteBulkObject
(
fp
,
&
key
)
==
0
)
goto
werr
;
if
(
fwriteBulkObject
(
fp
,
eleobj
)
==
0
)
goto
werr
;
if
(
fwriteBulkObject
(
fp
,
eleobj
)
==
0
)
goto
werr
;
}
}
dictReleaseIterator
(
di
);
dictReleaseIterator
(
di
);
}
else
{
redisPanic
(
"Unknown set encoding"
);
}
}
else
if
(
o
->
type
==
REDIS_ZSET
)
{
}
else
if
(
o
->
type
==
REDIS_ZSET
)
{
/* Emit the ZADDs needed to rebuild the sorted set */
/* Emit the ZADDs needed to rebuild the sorted set */
zset
*
zs
=
o
->
ptr
;
zset
*
zs
=
o
->
ptr
;
...
...
src/config.c
View file @
acc75bfd
...
@@ -199,6 +199,8 @@ void loadServerConfig(char *filename) {
...
@@ -199,6 +199,8 @@ void loadServerConfig(char *filename) {
server
.
list_max_ziplist_entries
=
memtoll
(
argv
[
1
],
NULL
);
server
.
list_max_ziplist_entries
=
memtoll
(
argv
[
1
],
NULL
);
}
else
if
(
!
strcasecmp
(
argv
[
0
],
"list-max-ziplist-value"
)
&&
argc
==
2
){
}
else
if
(
!
strcasecmp
(
argv
[
0
],
"list-max-ziplist-value"
)
&&
argc
==
2
){
server
.
list_max_ziplist_value
=
memtoll
(
argv
[
1
],
NULL
);
server
.
list_max_ziplist_value
=
memtoll
(
argv
[
1
],
NULL
);
}
else
if
(
!
strcasecmp
(
argv
[
0
],
"set-max-intset-entries"
)
&&
argc
==
2
){
server
.
set_max_intset_entries
=
memtoll
(
argv
[
1
],
NULL
);
}
else
{
}
else
{
err
=
"Bad directive or wrong number of arguments"
;
goto
loaderr
;
err
=
"Bad directive or wrong number of arguments"
;
goto
loaderr
;
}
}
...
...
src/debug.c
View file @
acc75bfd
...
@@ -119,16 +119,13 @@ void computeDatasetDigest(unsigned char *final) {
...
@@ -119,16 +119,13 @@ void computeDatasetDigest(unsigned char *final) {
}
}
listTypeReleaseIterator
(
li
);
listTypeReleaseIterator
(
li
);
}
else
if
(
o
->
type
==
REDIS_SET
)
{
}
else
if
(
o
->
type
==
REDIS_SET
)
{
dict
*
set
=
o
->
ptr
;
setTypeIterator
*
si
=
setTypeInitIterator
(
o
);
dictIterator
*
di
=
dictGetIterator
(
set
);
robj
*
ele
;
dictEntry
*
de
;
while
((
ele
=
setTypeNext
(
si
))
!=
NULL
)
{
xorObjectDigest
(
digest
,
ele
);
while
((
de
=
dictNext
(
di
))
!=
NULL
)
{
decrRefCount
(
ele
);
robj
*
eleobj
=
dictGetEntryKey
(
de
);
xorObjectDigest
(
digest
,
eleobj
);
}
}
dict
ReleaseIterator
(
d
i
);
setType
ReleaseIterator
(
s
i
);
}
else
if
(
o
->
type
==
REDIS_ZSET
)
{
}
else
if
(
o
->
type
==
REDIS_ZSET
)
{
zset
*
zs
=
o
->
ptr
;
zset
*
zs
=
o
->
ptr
;
dictIterator
*
di
=
dictGetIterator
(
zs
->
dict
);
dictIterator
*
di
=
dictGetIterator
(
zs
->
dict
);
...
...
src/intset.c
0 → 100644
View file @
acc75bfd
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "intset.h"
#include "zmalloc.h"
/* Note that these encodings are ordered, so:
* INTSET_ENC_INT16 < INTSET_ENC_INT32 < INTSET_ENC_INT64. */
#define INTSET_ENC_INT16 (sizeof(int16_t))
#define INTSET_ENC_INT32 (sizeof(int32_t))
#define INTSET_ENC_INT64 (sizeof(int64_t))
/* Return the required encoding for the provided value. */
static
uint8_t
_intsetValueEncoding
(
int64_t
v
)
{
if
(
v
<
INT32_MIN
||
v
>
INT32_MAX
)
return
INTSET_ENC_INT64
;
else
if
(
v
<
INT16_MIN
||
v
>
INT16_MAX
)
return
INTSET_ENC_INT32
;
return
INTSET_ENC_INT16
;
}
/* Return the value at pos, given an encoding. */
static
int64_t
_intsetGetEncoded
(
intset
*
is
,
int
pos
,
uint8_t
enc
)
{
if
(
enc
==
INTSET_ENC_INT64
)
return
((
int64_t
*
)
is
->
contents
)[
pos
];
else
if
(
enc
==
INTSET_ENC_INT32
)
return
((
int32_t
*
)
is
->
contents
)[
pos
];
return
((
int16_t
*
)
is
->
contents
)[
pos
];
}
/* Return the value at pos, using the configured encoding. */
static
int64_t
_intsetGet
(
intset
*
is
,
int
pos
)
{
return
_intsetGetEncoded
(
is
,
pos
,
is
->
encoding
);
}
/* Set the value at pos, using the configured encoding. */
static
void
_intsetSet
(
intset
*
is
,
int
pos
,
int64_t
value
)
{
if
(
is
->
encoding
==
INTSET_ENC_INT64
)
((
int64_t
*
)
is
->
contents
)[
pos
]
=
value
;
else
if
(
is
->
encoding
==
INTSET_ENC_INT32
)
((
int32_t
*
)
is
->
contents
)[
pos
]
=
value
;
else
((
int16_t
*
)
is
->
contents
)[
pos
]
=
value
;
}
/* Create an empty intset. */
intset
*
intsetNew
(
void
)
{
intset
*
is
=
zmalloc
(
sizeof
(
intset
));
is
->
encoding
=
INTSET_ENC_INT16
;
is
->
length
=
0
;
return
is
;
}
/* Resize the intset */
static
intset
*
intsetResize
(
intset
*
is
,
uint32_t
len
)
{
uint32_t
size
=
len
*
is
->
encoding
;
is
=
zrealloc
(
is
,
sizeof
(
intset
)
+
size
);
return
is
;
}
/* Search for the position of "value". Return 1 when the value was found and
* sets "pos" to the position of the value within the intset. Return 0 when
* the value is not present in the intset and sets "pos" to the position
* where "value" can be inserted. */
static
uint8_t
intsetSearch
(
intset
*
is
,
int64_t
value
,
uint32_t
*
pos
)
{
int
min
=
0
,
max
=
is
->
length
-
1
,
mid
=
-
1
;
int64_t
cur
=
-
1
;
/* The value can never be found when the set is empty */
if
(
is
->
length
==
0
)
{
if
(
pos
)
*
pos
=
0
;
return
0
;
}
else
{
/* Check for the case where we know we cannot find the value,
* but do know the insert position. */
if
(
value
>
_intsetGet
(
is
,
is
->
length
-
1
))
{
if
(
pos
)
*
pos
=
is
->
length
;
return
0
;
}
else
if
(
value
<
_intsetGet
(
is
,
0
))
{
if
(
pos
)
*
pos
=
0
;
return
0
;
}
}
while
(
max
>=
min
)
{
mid
=
(
min
+
max
)
/
2
;
cur
=
_intsetGet
(
is
,
mid
);
if
(
value
>
cur
)
{
min
=
mid
+
1
;
}
else
if
(
value
<
cur
)
{
max
=
mid
-
1
;
}
else
{
break
;
}
}
if
(
value
==
cur
)
{
if
(
pos
)
*
pos
=
mid
;
return
1
;
}
else
{
if
(
pos
)
*
pos
=
min
;
return
0
;
}
}
/* Upgrades the intset to a larger encoding and inserts the given integer. */
static
intset
*
intsetUpgradeAndAdd
(
intset
*
is
,
int64_t
value
)
{
uint8_t
curenc
=
is
->
encoding
;
uint8_t
newenc
=
_intsetValueEncoding
(
value
);
int
length
=
is
->
length
;
int
prepend
=
value
<
0
?
1
:
0
;
/* First set new encoding and resize */
is
->
encoding
=
newenc
;
is
=
intsetResize
(
is
,
is
->
length
+
1
);
/* Upgrade back-to-front so we don't overwrite values.
* Note that the "prepend" variable is used to make sure we have an empty
* space at either the beginning or the end of the intset. */
while
(
length
--
)
_intsetSet
(
is
,
length
+
prepend
,
_intsetGetEncoded
(
is
,
length
,
curenc
));
/* Set the value at the beginning or the end. */
if
(
prepend
)
_intsetSet
(
is
,
0
,
value
);
else
_intsetSet
(
is
,
is
->
length
,
value
);
is
->
length
++
;
return
is
;
}
static
void
intsetMoveTail
(
intset
*
is
,
uint32_t
from
,
uint32_t
to
)
{
void
*
src
,
*
dst
;
uint32_t
bytes
=
is
->
length
-
from
;
if
(
is
->
encoding
==
INTSET_ENC_INT64
)
{
src
=
(
int64_t
*
)
is
->
contents
+
from
;
dst
=
(
int64_t
*
)
is
->
contents
+
to
;
bytes
*=
sizeof
(
int64_t
);
}
else
if
(
is
->
encoding
==
INTSET_ENC_INT32
)
{
src
=
(
int32_t
*
)
is
->
contents
+
from
;
dst
=
(
int32_t
*
)
is
->
contents
+
to
;
bytes
*=
sizeof
(
int32_t
);
}
else
{
src
=
(
int16_t
*
)
is
->
contents
+
from
;
dst
=
(
int16_t
*
)
is
->
contents
+
to
;
bytes
*=
sizeof
(
int16_t
);
}
memmove
(
dst
,
src
,
bytes
);
}
/* Insert an integer in the intset */
intset
*
intsetAdd
(
intset
*
is
,
int64_t
value
,
uint8_t
*
success
)
{
uint8_t
valenc
=
_intsetValueEncoding
(
value
);
uint32_t
pos
,
offset
;
if
(
success
)
*
success
=
1
;
/* Upgrade encoding if necessary. If we need to upgrade, we know that
* this value should be either appended (if > 0) or prepended (if < 0),
* because it lies outside the range of existing values. */
if
(
valenc
>
is
->
encoding
)
{
/* This always succeeds, so we don't need to curry *success. */
return
intsetUpgradeAndAdd
(
is
,
value
);
}
else
{
/* Abort if the value is already present in the set.
* This call will populate "pos" with the right position to insert
* the value when it cannot be found. */
if
(
intsetSearch
(
is
,
value
,
&
pos
))
{
if
(
success
)
*
success
=
0
;
return
is
;
}
is
=
intsetResize
(
is
,
is
->
length
+
1
);
if
(
pos
<
is
->
length
)
intsetMoveTail
(
is
,
pos
,
pos
+
1
);
}
_intsetSet
(
is
,
pos
,
value
);
is
->
length
++
;
return
is
;
}
/* Delete integer from intset */
intset
*
intsetRemove
(
intset
*
is
,
int64_t
value
,
uint8_t
*
success
)
{
uint8_t
valenc
=
_intsetValueEncoding
(
value
);
uint32_t
pos
;
if
(
success
)
*
success
=
0
;
if
(
valenc
<=
is
->
encoding
&&
intsetSearch
(
is
,
value
,
&
pos
))
{
/* We know we can delete */
if
(
success
)
*
success
=
1
;
/* Overwrite value with tail and update length */
if
(
pos
<
(
is
->
length
-
1
))
intsetMoveTail
(
is
,
pos
+
1
,
pos
);
is
=
intsetResize
(
is
,
is
->
length
-
1
);
is
->
length
--
;
}
return
is
;
}
/* Determine whether a value belongs to this set */
uint8_t
intsetFind
(
intset
*
is
,
int64_t
value
)
{
uint8_t
valenc
=
_intsetValueEncoding
(
value
);
return
valenc
<=
is
->
encoding
&&
intsetSearch
(
is
,
value
,
NULL
);
}
/* Return random member */
int64_t
intsetRandom
(
intset
*
is
)
{
return
_intsetGet
(
is
,
rand
()
%
is
->
length
);
}
/* Sets the value to the value at the given position. When this position is
* out of range the function returns 0, when in range it returns 1. */
uint8_t
intsetGet
(
intset
*
is
,
uint32_t
pos
,
int64_t
*
value
)
{
if
(
pos
<
is
->
length
)
{
*
value
=
_intsetGet
(
is
,
pos
);
return
1
;
}
return
0
;
}
/* Return intset length */
uint32_t
intsetLen
(
intset
*
is
)
{
return
is
->
length
;
}
#ifdef INTSET_TEST_MAIN
#include <sys/time.h>
void
intsetRepr
(
intset
*
is
)
{
int
i
;
for
(
i
=
0
;
i
<
is
->
length
;
i
++
)
{
printf
(
"%lld
\n
"
,
(
uint64_t
)
_intsetGet
(
is
,
i
));
}
printf
(
"
\n
"
);
}
void
error
(
char
*
err
)
{
printf
(
"%s
\n
"
,
err
);
exit
(
1
);
}
void
ok
(
void
)
{
printf
(
"OK
\n
"
);
}
long
long
usec
(
void
)
{
struct
timeval
tv
;
gettimeofday
(
&
tv
,
NULL
);
return
(((
long
long
)
tv
.
tv_sec
)
*
1000000
)
+
tv
.
tv_usec
;
}
#define assert(_e) ((_e)?(void)0:(_assert(#_e,__FILE__,__LINE__),exit(1)))
void
_assert
(
char
*
estr
,
char
*
file
,
int
line
)
{
printf
(
"
\n\n
=== ASSERTION FAILED ===
\n
"
);
printf
(
"==> %s:%d '%s' is not true
\n
"
,
file
,
line
,
estr
);
}
intset
*
createSet
(
int
bits
,
int
size
)
{
uint64_t
mask
=
(
1
<<
bits
)
-
1
;
uint64_t
i
,
value
;
intset
*
is
=
intsetNew
();
for
(
i
=
0
;
i
<
size
;
i
++
)
{
if
(
bits
>
32
)
{
value
=
(
rand
()
*
rand
())
&
mask
;
}
else
{
value
=
rand
()
&
mask
;
}
is
=
intsetAdd
(
is
,
value
,
NULL
);
}
return
is
;
}
void
checkConsistency
(
intset
*
is
)
{
int
i
;
for
(
i
=
0
;
i
<
(
is
->
length
-
1
);
i
++
)
{
if
(
is
->
encoding
==
INTSET_ENC_INT16
)
{
int16_t
*
i16
=
(
int16_t
*
)
is
->
contents
;
assert
(
i16
[
i
]
<
i16
[
i
+
1
]);
}
else
if
(
is
->
encoding
==
INTSET_ENC_INT32
)
{
int32_t
*
i32
=
(
int32_t
*
)
is
->
contents
;
assert
(
i32
[
i
]
<
i32
[
i
+
1
]);
}
else
{
int64_t
*
i64
=
(
int64_t
*
)
is
->
contents
;
assert
(
i64
[
i
]
<
i64
[
i
+
1
]);
}
}
}
int
main
(
int
argc
,
char
**
argv
)
{
uint8_t
success
;
int
i
;
intset
*
is
;
sranddev
();
printf
(
"Value encodings: "
);
{
assert
(
_intsetValueEncoding
(
-
32768
)
==
INTSET_ENC_INT16
);
assert
(
_intsetValueEncoding
(
+
32767
)
==
INTSET_ENC_INT16
);
assert
(
_intsetValueEncoding
(
-
32769
)
==
INTSET_ENC_INT32
);
assert
(
_intsetValueEncoding
(
+
32768
)
==
INTSET_ENC_INT32
);
assert
(
_intsetValueEncoding
(
-
2147483648
)
==
INTSET_ENC_INT32
);
assert
(
_intsetValueEncoding
(
+
2147483647
)
==
INTSET_ENC_INT32
);
assert
(
_intsetValueEncoding
(
-
2147483649
)
==
INTSET_ENC_INT64
);
assert
(
_intsetValueEncoding
(
+
2147483648
)
==
INTSET_ENC_INT64
);
assert
(
_intsetValueEncoding
(
-
9223372036854775808ull
)
==
INTSET_ENC_INT64
);
assert
(
_intsetValueEncoding
(
+
9223372036854775807ull
)
==
INTSET_ENC_INT64
);
ok
();
}
printf
(
"Basic adding: "
);
{
is
=
intsetNew
();
is
=
intsetAdd
(
is
,
5
,
&
success
);
assert
(
success
);
is
=
intsetAdd
(
is
,
6
,
&
success
);
assert
(
success
);
is
=
intsetAdd
(
is
,
4
,
&
success
);
assert
(
success
);
is
=
intsetAdd
(
is
,
4
,
&
success
);
assert
(
!
success
);
ok
();
}
printf
(
"Large number of random adds: "
);
{
int
inserts
=
0
;
is
=
intsetNew
();
for
(
i
=
0
;
i
<
1024
;
i
++
)
{
is
=
intsetAdd
(
is
,
rand
()
%
0x800
,
&
success
);
if
(
success
)
inserts
++
;
}
assert
(
is
->
length
==
inserts
);
checkConsistency
(
is
);
ok
();
}
printf
(
"Upgrade from int16 to int32: "
);
{
is
=
intsetNew
();
is
=
intsetAdd
(
is
,
32
,
NULL
);
assert
(
is
->
encoding
==
INTSET_ENC_INT16
);
is
=
intsetAdd
(
is
,
65535
,
NULL
);
assert
(
is
->
encoding
==
INTSET_ENC_INT32
);
assert
(
intsetFind
(
is
,
32
));
assert
(
intsetFind
(
is
,
65535
));
checkConsistency
(
is
);
is
=
intsetNew
();
is
=
intsetAdd
(
is
,
32
,
NULL
);
assert
(
is
->
encoding
==
INTSET_ENC_INT16
);
is
=
intsetAdd
(
is
,
-
65535
,
NULL
);
assert
(
is
->
encoding
==
INTSET_ENC_INT32
);
assert
(
intsetFind
(
is
,
32
));
assert
(
intsetFind
(
is
,
-
65535
));
checkConsistency
(
is
);
ok
();
}
printf
(
"Upgrade from int16 to int64: "
);
{
is
=
intsetNew
();
is
=
intsetAdd
(
is
,
32
,
NULL
);
assert
(
is
->
encoding
==
INTSET_ENC_INT16
);
is
=
intsetAdd
(
is
,
4294967295
,
NULL
);
assert
(
is
->
encoding
==
INTSET_ENC_INT64
);
assert
(
intsetFind
(
is
,
32
));
assert
(
intsetFind
(
is
,
4294967295
));
checkConsistency
(
is
);
is
=
intsetNew
();
is
=
intsetAdd
(
is
,
32
,
NULL
);
assert
(
is
->
encoding
==
INTSET_ENC_INT16
);
is
=
intsetAdd
(
is
,
-
4294967295
,
NULL
);
assert
(
is
->
encoding
==
INTSET_ENC_INT64
);
assert
(
intsetFind
(
is
,
32
));
assert
(
intsetFind
(
is
,
-
4294967295
));
checkConsistency
(
is
);
ok
();
}
printf
(
"Upgrade from int32 to int64: "
);
{
is
=
intsetNew
();
is
=
intsetAdd
(
is
,
65535
,
NULL
);
assert
(
is
->
encoding
==
INTSET_ENC_INT32
);
is
=
intsetAdd
(
is
,
4294967295
,
NULL
);
assert
(
is
->
encoding
==
INTSET_ENC_INT64
);
assert
(
intsetFind
(
is
,
65535
));
assert
(
intsetFind
(
is
,
4294967295
));
checkConsistency
(
is
);
is
=
intsetNew
();
is
=
intsetAdd
(
is
,
65535
,
NULL
);
assert
(
is
->
encoding
==
INTSET_ENC_INT32
);
is
=
intsetAdd
(
is
,
-
4294967295
,
NULL
);
assert
(
is
->
encoding
==
INTSET_ENC_INT64
);
assert
(
intsetFind
(
is
,
65535
));
assert
(
intsetFind
(
is
,
-
4294967295
));
checkConsistency
(
is
);
ok
();
}
printf
(
"Stress lookups: "
);
{
long
num
=
100000
,
size
=
10000
;
int
i
,
bits
=
20
;
long
long
start
;
is
=
createSet
(
bits
,
size
);
checkConsistency
(
is
);
start
=
usec
();
for
(
i
=
0
;
i
<
num
;
i
++
)
intsetSearch
(
is
,
rand
()
%
((
1
<<
bits
)
-
1
),
NULL
);
printf
(
"%ld lookups, %ld element set, %lldusec
\n
"
,
num
,
size
,
usec
()
-
start
);
}
printf
(
"Stress add+delete: "
);
{
int
i
,
v1
,
v2
;
is
=
intsetNew
();
for
(
i
=
0
;
i
<
0xffff
;
i
++
)
{
v1
=
rand
()
%
0xfff
;
is
=
intsetAdd
(
is
,
v1
,
NULL
);
assert
(
intsetFind
(
is
,
v1
));
v2
=
rand
()
%
0xfff
;
is
=
intsetRemove
(
is
,
v2
,
NULL
);
assert
(
!
intsetFind
(
is
,
v2
));
}
checkConsistency
(
is
);
ok
();
}
}
#endif
src/intset.h
0 → 100644
View file @
acc75bfd
#ifndef __INTSET_H
#define __INTSET_H
#include <stdint.h>
typedef
struct
intset
{
uint32_t
encoding
;
uint32_t
length
;
int8_t
contents
[];
}
intset
;
intset
*
intsetNew
(
void
);
intset
*
intsetAdd
(
intset
*
is
,
int64_t
value
,
uint8_t
*
success
);
intset
*
intsetRemove
(
intset
*
is
,
int64_t
value
,
uint8_t
*
success
);
uint8_t
intsetFind
(
intset
*
is
,
int64_t
value
);
int64_t
intsetRandom
(
intset
*
is
);
uint8_t
intsetGet
(
intset
*
is
,
uint32_t
pos
,
int64_t
*
value
);
uint32_t
intsetLen
(
intset
*
is
);
#endif // __INTSET_H
src/object.c
View file @
acc75bfd
...
@@ -74,7 +74,16 @@ robj *createZiplistObject(void) {
...
@@ -74,7 +74,16 @@ robj *createZiplistObject(void) {
robj
*
createSetObject
(
void
)
{
robj
*
createSetObject
(
void
)
{
dict
*
d
=
dictCreate
(
&
setDictType
,
NULL
);
dict
*
d
=
dictCreate
(
&
setDictType
,
NULL
);
return
createObject
(
REDIS_SET
,
d
);
robj
*
o
=
createObject
(
REDIS_SET
,
d
);
o
->
encoding
=
REDIS_ENCODING_HT
;
return
o
;
}
robj
*
createIntsetObject
(
void
)
{
intset
*
is
=
intsetNew
();
robj
*
o
=
createObject
(
REDIS_SET
,
is
);
o
->
encoding
=
REDIS_ENCODING_INTSET
;
return
o
;
}
}
robj
*
createHashObject
(
void
)
{
robj
*
createHashObject
(
void
)
{
...
@@ -115,7 +124,16 @@ void freeListObject(robj *o) {
...
@@ -115,7 +124,16 @@ void freeListObject(robj *o) {
}
}
void
freeSetObject
(
robj
*
o
)
{
void
freeSetObject
(
robj
*
o
)
{
switch
(
o
->
encoding
)
{
case
REDIS_ENCODING_HT
:
dictRelease
((
dict
*
)
o
->
ptr
);
dictRelease
((
dict
*
)
o
->
ptr
);
break
;
case
REDIS_ENCODING_INTSET
:
zfree
(
o
->
ptr
);
break
;
default:
redisPanic
(
"Unknown set encoding type"
);
}
}
}
void
freeZsetObject
(
robj
*
o
)
{
void
freeZsetObject
(
robj
*
o
)
{
...
@@ -357,6 +375,7 @@ int getLongLongFromObject(robj *o, long long *target) {
...
@@ -357,6 +375,7 @@ int getLongLongFromObject(robj *o, long long *target) {
redisAssert
(
o
->
type
==
REDIS_STRING
);
redisAssert
(
o
->
type
==
REDIS_STRING
);
if
(
o
->
encoding
==
REDIS_ENCODING_RAW
)
{
if
(
o
->
encoding
==
REDIS_ENCODING_RAW
)
{
value
=
strtoll
(
o
->
ptr
,
&
eptr
,
10
);
value
=
strtoll
(
o
->
ptr
,
&
eptr
,
10
);
if
(
errno
==
ERANGE
)
return
REDIS_ERR
;
if
(
eptr
[
0
]
!=
'\0'
)
return
REDIS_ERR
;
if
(
eptr
[
0
]
!=
'\0'
)
return
REDIS_ERR
;
if
(
errno
==
ERANGE
&&
(
value
==
LLONG_MIN
||
value
==
LLONG_MAX
))
if
(
errno
==
ERANGE
&&
(
value
==
LLONG_MIN
||
value
==
LLONG_MAX
))
return
REDIS_ERR
;
return
REDIS_ERR
;
...
@@ -367,7 +386,7 @@ int getLongLongFromObject(robj *o, long long *target) {
...
@@ -367,7 +386,7 @@ int getLongLongFromObject(robj *o, long long *target) {
}
}
}
}
*
target
=
value
;
if
(
target
)
*
target
=
value
;
return
REDIS_OK
;
return
REDIS_OK
;
}
}
...
@@ -411,6 +430,7 @@ char *strEncoding(int encoding) {
...
@@ -411,6 +430,7 @@ char *strEncoding(int encoding) {
case
REDIS_ENCODING_ZIPMAP
:
return
"zipmap"
;
case
REDIS_ENCODING_ZIPMAP
:
return
"zipmap"
;
case
REDIS_ENCODING_LINKEDLIST
:
return
"linkedlist"
;
case
REDIS_ENCODING_LINKEDLIST
:
return
"linkedlist"
;
case
REDIS_ENCODING_ZIPLIST
:
return
"ziplist"
;
case
REDIS_ENCODING_ZIPLIST
:
return
"ziplist"
;
case
REDIS_ENCODING_INTSET
:
return
"intset"
;
default:
return
"unknown"
;
default:
return
"unknown"
;
}
}
}
}
src/rdb.c
View file @
acc75bfd
...
@@ -260,6 +260,7 @@ int rdbSaveObject(FILE *fp, robj *o) {
...
@@ -260,6 +260,7 @@ int rdbSaveObject(FILE *fp, robj *o) {
}
}
}
else
if
(
o
->
type
==
REDIS_SET
)
{
}
else
if
(
o
->
type
==
REDIS_SET
)
{
/* Save a set value */
/* Save a set value */
if
(
o
->
encoding
==
REDIS_ENCODING_HT
)
{
dict
*
set
=
o
->
ptr
;
dict
*
set
=
o
->
ptr
;
dictIterator
*
di
=
dictGetIterator
(
set
);
dictIterator
*
di
=
dictGetIterator
(
set
);
dictEntry
*
de
;
dictEntry
*
de
;
...
@@ -267,10 +268,21 @@ int rdbSaveObject(FILE *fp, robj *o) {
...
@@ -267,10 +268,21 @@ int rdbSaveObject(FILE *fp, robj *o) {
if
(
rdbSaveLen
(
fp
,
dictSize
(
set
))
==
-
1
)
return
-
1
;
if
(
rdbSaveLen
(
fp
,
dictSize
(
set
))
==
-
1
)
return
-
1
;
while
((
de
=
dictNext
(
di
))
!=
NULL
)
{
while
((
de
=
dictNext
(
di
))
!=
NULL
)
{
robj
*
eleobj
=
dictGetEntryKey
(
de
);
robj
*
eleobj
=
dictGetEntryKey
(
de
);
if
(
rdbSaveStringObject
(
fp
,
eleobj
)
==
-
1
)
return
-
1
;
if
(
rdbSaveStringObject
(
fp
,
eleobj
)
==
-
1
)
return
-
1
;
}
}
dictReleaseIterator
(
di
);
dictReleaseIterator
(
di
);
}
else
if
(
o
->
encoding
==
REDIS_ENCODING_INTSET
)
{
intset
*
is
=
o
->
ptr
;
long
long
llval
;
int
i
=
0
;
if
(
rdbSaveLen
(
fp
,
intsetLen
(
is
))
==
-
1
)
return
-
1
;
while
(
intsetGet
(
is
,
i
++
,
&
llval
))
{
if
(
rdbSaveLongLongAsStringObject
(
fp
,
llval
)
==
-
1
)
return
-
1
;
}
}
else
{
redisPanic
(
"Unknown set encoding"
);
}
}
else
if
(
o
->
type
==
REDIS_ZSET
)
{
}
else
if
(
o
->
type
==
REDIS_ZSET
)
{
/* Save a set value */
/* Save a set value */
zset
*
zs
=
o
->
ptr
;
zset
*
zs
=
o
->
ptr
;
...
@@ -628,6 +640,7 @@ int rdbLoadDoubleValue(FILE *fp, double *val) {
...
@@ -628,6 +640,7 @@ int rdbLoadDoubleValue(FILE *fp, double *val) {
robj
*
rdbLoadObject
(
int
type
,
FILE
*
fp
)
{
robj
*
rdbLoadObject
(
int
type
,
FILE
*
fp
)
{
robj
*
o
,
*
ele
,
*
dec
;
robj
*
o
,
*
ele
,
*
dec
;
size_t
len
;
size_t
len
;
unsigned
int
i
;
redisLog
(
REDIS_DEBUG
,
"LOADING OBJECT %d (at %d)
\n
"
,
type
,
ftell
(
fp
));
redisLog
(
REDIS_DEBUG
,
"LOADING OBJECT %d (at %d)
\n
"
,
type
,
ftell
(
fp
));
if
(
type
==
REDIS_STRING
)
{
if
(
type
==
REDIS_STRING
)
{
...
@@ -669,17 +682,40 @@ robj *rdbLoadObject(int type, FILE *fp) {
...
@@ -669,17 +682,40 @@ robj *rdbLoadObject(int type, FILE *fp) {
}
else
if
(
type
==
REDIS_SET
)
{
}
else
if
(
type
==
REDIS_SET
)
{
/* Read list/set value */
/* Read list/set value */
if
((
len
=
rdbLoadLen
(
fp
,
NULL
))
==
REDIS_RDB_LENERR
)
return
NULL
;
if
((
len
=
rdbLoadLen
(
fp
,
NULL
))
==
REDIS_RDB_LENERR
)
return
NULL
;
/* Use a regular set when there are too many entries. */
if
(
len
>
server
.
set_max_intset_entries
)
{
o
=
createSetObject
();
o
=
createSetObject
();
/* It's faster to expand the dict to the right size asap in order
/* It's faster to expand the dict to the right size asap in order
* to avoid rehashing */
* to avoid rehashing */
if
(
len
>
DICT_HT_INITIAL_SIZE
)
if
(
len
>
DICT_HT_INITIAL_SIZE
)
dictExpand
(
o
->
ptr
,
len
);
dictExpand
(
o
->
ptr
,
len
);
}
else
{
o
=
createIntsetObject
();
}
/* Load every single element of the list/set */
/* Load every single element of the list/set */
while
(
len
--
)
{
for
(
i
=
0
;
i
<
len
;
i
++
)
{
long
long
llval
;
if
((
ele
=
rdbLoadEncodedStringObject
(
fp
))
==
NULL
)
return
NULL
;
if
((
ele
=
rdbLoadEncodedStringObject
(
fp
))
==
NULL
)
return
NULL
;
ele
=
tryObjectEncoding
(
ele
);
ele
=
tryObjectEncoding
(
ele
);
if
(
o
->
encoding
==
REDIS_ENCODING_INTSET
)
{
/* Fetch integer value from element */
if
(
getLongLongFromObject
(
ele
,
&
llval
)
==
REDIS_OK
)
{
o
->
ptr
=
intsetAdd
(
o
->
ptr
,
llval
,
NULL
);
}
else
{
setTypeConvert
(
o
,
REDIS_ENCODING_HT
);
dictExpand
(
o
->
ptr
,
len
);
}
}
/* This will also be called when the set was just converted
* to regular hashtable encoded set */
if
(
o
->
encoding
==
REDIS_ENCODING_HT
)
{
dictAdd
((
dict
*
)
o
->
ptr
,
ele
,
NULL
);
dictAdd
((
dict
*
)
o
->
ptr
,
ele
,
NULL
);
}
}
}
}
else
if
(
type
==
REDIS_ZSET
)
{
}
else
if
(
type
==
REDIS_ZSET
)
{
/* Read list/set value */
/* Read list/set value */
size_t
zsetlen
;
size_t
zsetlen
;
...
...
src/redis.c
View file @
acc75bfd
...
@@ -743,6 +743,7 @@ void initServerConfig() {
...
@@ -743,6 +743,7 @@ void initServerConfig() {
server
.
hash_max_zipmap_value
=
REDIS_HASH_MAX_ZIPMAP_VALUE
;
server
.
hash_max_zipmap_value
=
REDIS_HASH_MAX_ZIPMAP_VALUE
;
server
.
list_max_ziplist_entries
=
REDIS_LIST_MAX_ZIPLIST_ENTRIES
;
server
.
list_max_ziplist_entries
=
REDIS_LIST_MAX_ZIPLIST_ENTRIES
;
server
.
list_max_ziplist_value
=
REDIS_LIST_MAX_ZIPLIST_VALUE
;
server
.
list_max_ziplist_value
=
REDIS_LIST_MAX_ZIPLIST_VALUE
;
server
.
set_max_intset_entries
=
REDIS_SET_MAX_INTSET_ENTRIES
;
server
.
shutdown_asap
=
0
;
server
.
shutdown_asap
=
0
;
resetServerSaveParams
();
resetServerSaveParams
();
...
...
src/redis.h
View file @
acc75bfd
...
@@ -26,6 +26,7 @@
...
@@ -26,6 +26,7 @@
#include "anet.h"
/* Networking the easy way */
#include "anet.h"
/* Networking the easy way */
#include "zipmap.h"
/* Compact string -> string data structure */
#include "zipmap.h"
/* Compact string -> string data structure */
#include "ziplist.h"
/* Compact list data structure */
#include "ziplist.h"
/* Compact list data structure */
#include "intset.h"
/* Compact integer set structure */
#include "version.h"
#include "version.h"
/* Error codes */
/* Error codes */
...
@@ -82,6 +83,7 @@
...
@@ -82,6 +83,7 @@
#define REDIS_ENCODING_ZIPMAP 3
/* Encoded as zipmap */
#define REDIS_ENCODING_ZIPMAP 3
/* Encoded as zipmap */
#define REDIS_ENCODING_LINKEDLIST 4
/* Encoded as regular linked list */
#define REDIS_ENCODING_LINKEDLIST 4
/* Encoded as regular linked list */
#define REDIS_ENCODING_ZIPLIST 5
/* Encoded as ziplist */
#define REDIS_ENCODING_ZIPLIST 5
/* Encoded as ziplist */
#define REDIS_ENCODING_INTSET 6
/* Encoded as intset */
/* Object types only used for dumping to disk */
/* Object types only used for dumping to disk */
#define REDIS_EXPIRETIME 253
#define REDIS_EXPIRETIME 253
...
@@ -188,6 +190,7 @@
...
@@ -188,6 +190,7 @@
#define REDIS_HASH_MAX_ZIPMAP_VALUE 512
#define REDIS_HASH_MAX_ZIPMAP_VALUE 512
#define REDIS_LIST_MAX_ZIPLIST_ENTRIES 1024
#define REDIS_LIST_MAX_ZIPLIST_ENTRIES 1024
#define REDIS_LIST_MAX_ZIPLIST_VALUE 32
#define REDIS_LIST_MAX_ZIPLIST_VALUE 32
#define REDIS_SET_MAX_INTSET_ENTRIES 4096
/* Sets operations codes */
/* Sets operations codes */
#define REDIS_OP_UNION 0
#define REDIS_OP_UNION 0
...
@@ -398,6 +401,7 @@ struct redisServer {
...
@@ -398,6 +401,7 @@ struct redisServer {
size_t
hash_max_zipmap_value
;
size_t
hash_max_zipmap_value
;
size_t
list_max_ziplist_entries
;
size_t
list_max_ziplist_entries
;
size_t
list_max_ziplist_value
;
size_t
list_max_ziplist_value
;
size_t
set_max_intset_entries
;
/* Virtual memory state */
/* Virtual memory state */
FILE
*
vm_fp
;
FILE
*
vm_fp
;
int
vm_fd
;
int
vm_fd
;
...
@@ -535,6 +539,14 @@ typedef struct {
...
@@ -535,6 +539,14 @@ typedef struct {
listNode
*
ln
;
/* Entry in linked list */
listNode
*
ln
;
/* Entry in linked list */
}
listTypeEntry
;
}
listTypeEntry
;
/* Structure to hold set iteration abstraction. */
typedef
struct
{
robj
*
subject
;
int
encoding
;
int
ii
;
/* intset iterator */
dictIterator
*
di
;
}
setTypeIterator
;
/* Structure to hold hash iteration abstration. Note that iteration over
/* Structure to hold hash iteration abstration. Note that iteration over
* hashes involves both fields and values. Because it is possible that
* hashes involves both fields and values. Because it is possible that
* not both are required, store pointers in the iterator to avoid
* not both are required, store pointers in the iterator to avoid
...
@@ -633,6 +645,7 @@ robj *createStringObjectFromLongLong(long long value);
...
@@ -633,6 +645,7 @@ robj *createStringObjectFromLongLong(long long value);
robj
*
createListObject
(
void
);
robj
*
createListObject
(
void
);
robj
*
createZiplistObject
(
void
);
robj
*
createZiplistObject
(
void
);
robj
*
createSetObject
(
void
);
robj
*
createSetObject
(
void
);
robj
*
createIntsetObject
(
void
);
robj
*
createHashObject
(
void
);
robj
*
createHashObject
(
void
);
robj
*
createZsetObject
(
void
);
robj
*
createZsetObject
(
void
);
int
getLongFromObjectOrReply
(
redisClient
*
c
,
robj
*
o
,
long
*
target
,
const
char
*
msg
);
int
getLongFromObjectOrReply
(
redisClient
*
c
,
robj
*
o
,
long
*
target
,
const
char
*
msg
);
...
@@ -716,6 +729,18 @@ int dontWaitForSwappedKey(redisClient *c, robj *key);
...
@@ -716,6 +729,18 @@ int dontWaitForSwappedKey(redisClient *c, robj *key);
void
handleClientsBlockedOnSwappedKey
(
redisDb
*
db
,
robj
*
key
);
void
handleClientsBlockedOnSwappedKey
(
redisDb
*
db
,
robj
*
key
);
vmpointer
*
vmSwapObjectBlocking
(
robj
*
val
);
vmpointer
*
vmSwapObjectBlocking
(
robj
*
val
);
/* Set data type */
robj
*
setTypeCreate
(
robj
*
value
);
int
setTypeAdd
(
robj
*
subject
,
robj
*
value
);
int
setTypeRemove
(
robj
*
subject
,
robj
*
value
);
int
setTypeIsMember
(
robj
*
subject
,
robj
*
value
);
setTypeIterator
*
setTypeInitIterator
(
robj
*
subject
);
void
setTypeReleaseIterator
(
setTypeIterator
*
si
);
robj
*
setTypeNext
(
setTypeIterator
*
si
);
robj
*
setTypeRandomElement
(
robj
*
subject
);
unsigned
long
setTypeSize
(
robj
*
subject
);
void
setTypeConvert
(
robj
*
subject
,
int
enc
);
/* Hash data type */
/* Hash data type */
void
convertToRealHash
(
robj
*
o
);
void
convertToRealHash
(
robj
*
o
);
void
hashTypeTryConversion
(
robj
*
subject
,
robj
**
argv
,
int
start
,
int
end
);
void
hashTypeTryConversion
(
robj
*
subject
,
robj
**
argv
,
int
start
,
int
end
);
...
...
src/sort.c
View file @
acc75bfd
...
@@ -202,7 +202,7 @@ void sortCommand(redisClient *c) {
...
@@ -202,7 +202,7 @@ void sortCommand(redisClient *c) {
/* Load the sorting vector with all the objects to sort */
/* Load the sorting vector with all the objects to sort */
switch
(
sortval
->
type
)
{
switch
(
sortval
->
type
)
{
case
REDIS_LIST
:
vectorlen
=
listTypeLength
(
sortval
);
break
;
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
;
case
REDIS_ZSET
:
vectorlen
=
dictSize
(((
zset
*
)
sortval
->
ptr
)
->
dict
);
break
;
default:
vectorlen
=
0
;
redisPanic
(
"Bad SORT type"
);
/* Avoid GCC warning */
default:
vectorlen
=
0
;
redisPanic
(
"Bad SORT type"
);
/* Avoid GCC warning */
}
}
...
@@ -219,18 +219,20 @@ void sortCommand(redisClient *c) {
...
@@ -219,18 +219,20 @@ void sortCommand(redisClient *c) {
j
++
;
j
++
;
}
}
listTypeReleaseIterator
(
li
);
listTypeReleaseIterator
(
li
);
}
else
{
}
else
if
(
sortval
->
type
==
REDIS_SET
)
{
dict
*
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
;
dictIterator
*
di
;
dictEntry
*
setele
;
dictEntry
*
setele
;
if
(
sortval
->
type
==
REDIS_SET
)
{
set
=
sortval
->
ptr
;
}
else
{
zset
*
zs
=
sortval
->
ptr
;
set
=
zs
->
dict
;
}
di
=
dictGetIterator
(
set
);
di
=
dictGetIterator
(
set
);
while
((
setele
=
dictNext
(
di
))
!=
NULL
)
{
while
((
setele
=
dictNext
(
di
))
!=
NULL
)
{
vector
[
j
].
obj
=
dictGetEntryKey
(
setele
);
vector
[
j
].
obj
=
dictGetEntryKey
(
setele
);
...
@@ -239,6 +241,8 @@ void sortCommand(redisClient *c) {
...
@@ -239,6 +241,8 @@ void sortCommand(redisClient *c) {
j
++
;
j
++
;
}
}
dictReleaseIterator
(
di
);
dictReleaseIterator
(
di
);
}
else
{
redisPanic
(
"Unknown type"
);
}
}
redisAssert
(
j
==
vectorlen
);
redisAssert
(
j
==
vectorlen
);
...
@@ -369,7 +373,7 @@ void sortCommand(redisClient *c) {
...
@@ -369,7 +373,7 @@ void sortCommand(redisClient *c) {
}
}
/* Cleanup */
/* Cleanup */
if
(
sortval
->
type
==
REDIS_LIST
)
if
(
sortval
->
type
==
REDIS_LIST
||
sortval
->
type
==
REDIS_SET
)
for
(
j
=
0
;
j
<
vectorlen
;
j
++
)
for
(
j
=
0
;
j
<
vectorlen
;
j
++
)
decrRefCount
(
vector
[
j
].
obj
);
decrRefCount
(
vector
[
j
].
obj
);
decrRefCount
(
sortval
);
decrRefCount
(
sortval
);
...
...
src/t_set.c
View file @
acc75bfd
...
@@ -4,12 +4,182 @@
...
@@ -4,12 +4,182 @@
* Set Commands
* 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
(
getLongLongFromObject
(
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
(
getLongLongFromObject
(
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
(
getLongLongFromObject
(
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
(
getLongLongFromObject
(
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
(
setIterator
));
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
)
{
long
long
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
)
{
void
saddCommand
(
redisClient
*
c
)
{
robj
*
set
;
robj
*
set
;
set
=
lookupKeyWrite
(
c
->
db
,
c
->
argv
[
1
]);
set
=
lookupKeyWrite
(
c
->
db
,
c
->
argv
[
1
]);
if
(
set
==
NULL
)
{
if
(
set
==
NULL
)
{
set
=
createSetObject
(
);
set
=
setTypeCreate
(
c
->
argv
[
2
]
);
dbAdd
(
c
->
db
,
c
->
argv
[
1
],
set
);
dbAdd
(
c
->
db
,
c
->
argv
[
1
],
set
);
}
else
{
}
else
{
if
(
set
->
type
!=
REDIS_SET
)
{
if
(
set
->
type
!=
REDIS_SET
)
{
...
@@ -17,8 +187,7 @@ void saddCommand(redisClient *c) {
...
@@ -17,8 +187,7 @@ void saddCommand(redisClient *c) {
return
;
return
;
}
}
}
}
if
(
dictAdd
(
set
->
ptr
,
c
->
argv
[
2
],
NULL
)
==
DICT_OK
)
{
if
(
setTypeAdd
(
set
,
c
->
argv
[
2
]))
{
incrRefCount
(
c
->
argv
[
2
]);
touchWatchedKey
(
c
->
db
,
c
->
argv
[
1
]);
touchWatchedKey
(
c
->
db
,
c
->
argv
[
1
]);
server
.
dirty
++
;
server
.
dirty
++
;
addReply
(
c
,
shared
.
cone
);
addReply
(
c
,
shared
.
cone
);
...
@@ -33,11 +202,10 @@ void sremCommand(redisClient *c) {
...
@@ -33,11 +202,10 @@ void sremCommand(redisClient *c) {
if
((
set
=
lookupKeyWriteOrReply
(
c
,
c
->
argv
[
1
],
shared
.
czero
))
==
NULL
||
if
((
set
=
lookupKeyWriteOrReply
(
c
,
c
->
argv
[
1
],
shared
.
czero
))
==
NULL
||
checkType
(
c
,
set
,
REDIS_SET
))
return
;
checkType
(
c
,
set
,
REDIS_SET
))
return
;
if
(
dictDelete
(
set
->
ptr
,
c
->
argv
[
2
])
==
DICT_OK
)
{
if
(
setTypeRemove
(
set
,
c
->
argv
[
2
]))
{
server
.
dirty
++
;
if
(
setTypeSize
(
set
)
==
0
)
dbDelete
(
c
->
db
,
c
->
argv
[
1
])
;
touchWatchedKey
(
c
->
db
,
c
->
argv
[
1
]);
touchWatchedKey
(
c
->
db
,
c
->
argv
[
1
]);
if
(
htNeedsResize
(
set
->
ptr
))
dictResize
(
set
->
ptr
);
server
.
dirty
++
;
if
(
dictSize
((
dict
*
)
set
->
ptr
)
==
0
)
dbDelete
(
c
->
db
,
c
->
argv
[
1
]);
addReply
(
c
,
shared
.
cone
);
addReply
(
c
,
shared
.
cone
);
}
else
{
}
else
{
addReply
(
c
,
shared
.
czero
);
addReply
(
c
,
shared
.
czero
);
...
@@ -45,40 +213,48 @@ void sremCommand(redisClient *c) {
...
@@ -45,40 +213,48 @@ void sremCommand(redisClient *c) {
}
}
void
smoveCommand
(
redisClient
*
c
)
{
void
smoveCommand
(
redisClient
*
c
)
{
robj
*
srcset
,
*
dstset
;
robj
*
srcset
,
*
dstset
,
*
ele
;
srcset
=
lookupKeyWrite
(
c
->
db
,
c
->
argv
[
1
]);
srcset
=
lookupKeyWrite
(
c
->
db
,
c
->
argv
[
1
]);
dstset
=
lookupKeyWrite
(
c
->
db
,
c
->
argv
[
2
]);
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
/* If the source key does not exist return 0 */
* raise an error */
if
(
srcset
==
NULL
)
{
if
(
srcset
==
NULL
||
srcset
->
type
!=
REDIS_SET
)
{
addReply
(
c
,
shared
.
czero
);
addReply
(
c
,
srcset
?
shared
.
wrongtypeerr
:
shared
.
czero
);
return
;
return
;
}
}
/* Error if the destination key is not a set as well */
if
(
dstset
&&
dstset
->
type
!=
REDIS_SET
)
{
/* If the source key has the wrong type, or the destination key
addReply
(
c
,
shared
.
wrongtypeerr
);
* 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
;
return
;
}
}
/* Remove the element from the source set */
if
(
dictDelete
(
srcset
->
ptr
,
c
->
argv
[
3
])
==
DICT_ERR
)
{
/* If the element cannot be removed from the
src
set
, return 0. */
/* Key not found in the
src
set
! return zero */
if
(
!
setTypeRemove
(
srcset
,
ele
))
{
addReply
(
c
,
shared
.
czero
);
addReply
(
c
,
shared
.
czero
);
return
;
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
[
1
]);
touchWatchedKey
(
c
->
db
,
c
->
argv
[
2
]);
touchWatchedKey
(
c
->
db
,
c
->
argv
[
2
]);
server
.
dirty
++
;
server
.
dirty
++
;
/* Add the element to the destination set */
/* Create the destination set when it doesn't exist */
if
(
!
dstset
)
{
if
(
!
dstset
)
{
dstset
=
createSetObject
(
);
dstset
=
setTypeCreate
(
ele
);
dbAdd
(
c
->
db
,
c
->
argv
[
2
],
dstset
);
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
);
addReply
(
c
,
shared
.
cone
);
}
}
...
@@ -88,7 +264,7 @@ void sismemberCommand(redisClient *c) {
...
@@ -88,7 +264,7 @@ void sismemberCommand(redisClient *c) {
if
((
set
=
lookupKeyReadOrReply
(
c
,
c
->
argv
[
1
],
shared
.
czero
))
==
NULL
||
if
((
set
=
lookupKeyReadOrReply
(
c
,
c
->
argv
[
1
],
shared
.
czero
))
==
NULL
||
checkType
(
c
,
set
,
REDIS_SET
))
return
;
checkType
(
c
,
set
,
REDIS_SET
))
return
;
if
(
dictFind
(
set
->
ptr
,
c
->
argv
[
2
]))
if
(
setTypeIsMember
(
set
,
c
->
argv
[
2
]))
addReply
(
c
,
shared
.
cone
);
addReply
(
c
,
shared
.
cone
);
else
else
addReply
(
c
,
shared
.
czero
);
addReply
(
c
,
shared
.
czero
);
...
@@ -96,75 +272,63 @@ void sismemberCommand(redisClient *c) {
...
@@ -96,75 +272,63 @@ void sismemberCommand(redisClient *c) {
void
scardCommand
(
redisClient
*
c
)
{
void
scardCommand
(
redisClient
*
c
)
{
robj
*
o
;
robj
*
o
;
dict
*
s
;
if
((
o
=
lookupKeyReadOrReply
(
c
,
c
->
argv
[
1
],
shared
.
czero
))
==
NULL
||
if
((
o
=
lookupKeyReadOrReply
(
c
,
c
->
argv
[
1
],
shared
.
czero
))
==
NULL
||
checkType
(
c
,
o
,
REDIS_SET
))
return
;
checkType
(
c
,
o
,
REDIS_SET
))
return
;
s
=
o
->
ptr
;
addReplyUlong
(
c
,
setTypeSize
(
o
));
addReplyUlong
(
c
,
dictSize
(
s
));
}
}
void
spopCommand
(
redisClient
*
c
)
{
void
spopCommand
(
redisClient
*
c
)
{
robj
*
set
;
robj
*
set
,
*
ele
;
dictEntry
*
de
;
if
((
set
=
lookupKeyWriteOrReply
(
c
,
c
->
argv
[
1
],
shared
.
nullbulk
))
==
NULL
||
if
((
set
=
lookupKeyWriteOrReply
(
c
,
c
->
argv
[
1
],
shared
.
nullbulk
))
==
NULL
||
checkType
(
c
,
set
,
REDIS_SET
))
return
;
checkType
(
c
,
set
,
REDIS_SET
))
return
;
d
e
=
dictGetRandomKey
(
set
->
ptr
);
el
e
=
setTypeRandomElement
(
set
);
if
(
d
e
==
NULL
)
{
if
(
el
e
==
NULL
)
{
addReply
(
c
,
shared
.
nullbulk
);
addReply
(
c
,
shared
.
nullbulk
);
}
else
{
}
else
{
robj
*
ele
=
dictGetEntryKey
(
de
);
setTypeRemove
(
set
,
ele
);
addReplyBulk
(
c
,
ele
);
addReplyBulk
(
c
,
ele
);
dictDelete
(
set
->
ptr
,
ele
);
decrRefCount
(
ele
);
if
(
htNeedsResize
(
set
->
ptr
))
dictResize
(
set
->
ptr
);
if
(
setTypeSize
(
set
)
==
0
)
dbDelete
(
c
->
db
,
c
->
argv
[
1
]);
if
(
dictSize
((
dict
*
)
set
->
ptr
)
==
0
)
dbDelete
(
c
->
db
,
c
->
argv
[
1
]);
touchWatchedKey
(
c
->
db
,
c
->
argv
[
1
]);
touchWatchedKey
(
c
->
db
,
c
->
argv
[
1
]);
server
.
dirty
++
;
server
.
dirty
++
;
}
}
}
}
void
srandmemberCommand
(
redisClient
*
c
)
{
void
srandmemberCommand
(
redisClient
*
c
)
{
robj
*
set
;
robj
*
set
,
*
ele
;
dictEntry
*
de
;
if
((
set
=
lookupKeyReadOrReply
(
c
,
c
->
argv
[
1
],
shared
.
nullbulk
))
==
NULL
||
if
((
set
=
lookupKeyReadOrReply
(
c
,
c
->
argv
[
1
],
shared
.
nullbulk
))
==
NULL
||
checkType
(
c
,
set
,
REDIS_SET
))
return
;
checkType
(
c
,
set
,
REDIS_SET
))
return
;
d
e
=
dictGetRandomKey
(
set
->
ptr
);
el
e
=
setTypeRandomElement
(
set
);
if
(
d
e
==
NULL
)
{
if
(
el
e
==
NULL
)
{
addReply
(
c
,
shared
.
nullbulk
);
addReply
(
c
,
shared
.
nullbulk
);
}
else
{
}
else
{
robj
*
ele
=
dictGetEntryKey
(
de
);
addReplyBulk
(
c
,
ele
);
addReplyBulk
(
c
,
ele
);
decrRefCount
(
ele
);
}
}
}
}
int
qsortCompareSetsByCardinality
(
const
void
*
s1
,
const
void
*
s2
)
{
int
qsortCompareSetsByCardinality
(
const
void
*
s1
,
const
void
*
s2
)
{
dict
**
d1
=
(
void
*
)
s1
,
**
d2
=
(
void
*
)
s2
;
return
setTypeSize
(
*
(
robj
**
)
s1
)
-
setTypeSize
(
*
(
robj
**
)
s2
);
return
dictSize
(
*
d1
)
-
dictSize
(
*
d2
);
}
}
void
sinterGenericCommand
(
redisClient
*
c
,
robj
**
setskeys
,
unsigned
long
setsnum
,
robj
*
dstkey
)
{
void
sinterGenericCommand
(
redisClient
*
c
,
robj
**
setkeys
,
unsigned
long
setnum
,
robj
*
dstkey
)
{
dict
**
dv
=
zmalloc
(
sizeof
(
dict
*
)
*
setsnum
);
robj
**
sets
=
zmalloc
(
sizeof
(
robj
*
)
*
setnum
);
dictIterator
*
di
;
setTypeIterator
*
si
;
dictEntry
*
de
;
robj
*
ele
,
*
lenobj
=
NULL
,
*
dstset
=
NULL
;
robj
*
lenobj
=
NULL
,
*
dstset
=
NULL
;
unsigned
long
j
,
cardinality
=
0
;
unsigned
long
j
,
cardinality
=
0
;
for
(
j
=
0
;
j
<
setsnum
;
j
++
)
{
for
(
j
=
0
;
j
<
setnum
;
j
++
)
{
robj
*
setobj
;
robj
*
setobj
=
dstkey
?
lookupKeyWrite
(
c
->
db
,
setkeys
[
j
])
:
setobj
=
dstkey
?
lookupKeyRead
(
c
->
db
,
setkeys
[
j
]);
lookupKeyWrite
(
c
->
db
,
setskeys
[
j
])
:
lookupKeyRead
(
c
->
db
,
setskeys
[
j
]);
if
(
!
setobj
)
{
if
(
!
setobj
)
{
zfree
(
dv
);
zfree
(
sets
);
if
(
dstkey
)
{
if
(
dstkey
)
{
if
(
dbDelete
(
c
->
db
,
dstkey
))
{
if
(
dbDelete
(
c
->
db
,
dstkey
))
{
touchWatchedKey
(
c
->
db
,
dstkey
);
touchWatchedKey
(
c
->
db
,
dstkey
);
...
@@ -176,16 +340,15 @@ void sinterGenericCommand(redisClient *c, robj **setskeys, unsigned long setsnum
...
@@ -176,16 +340,15 @@ void sinterGenericCommand(redisClient *c, robj **setskeys, unsigned long setsnum
}
}
return
;
return
;
}
}
if
(
setobj
->
type
!=
REDIS_SET
)
{
if
(
checkType
(
c
,
setobj
,
REDIS_SET
))
{
zfree
(
dv
);
zfree
(
sets
);
addReply
(
c
,
shared
.
wrongtypeerr
);
return
;
return
;
}
}
dv
[
j
]
=
setobj
->
ptr
;
sets
[
j
]
=
setobj
;
}
}
/* Sort sets from the smallest to largest, this will improve our
/* Sort sets from the smallest to largest, this will improve our
* algorithm's performace */
* 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...
/* 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
* since this is a multi-bulk write, but at this stage we don't know
...
@@ -199,39 +362,37 @@ void sinterGenericCommand(redisClient *c, robj **setskeys, unsigned long setsnum
...
@@ -199,39 +362,37 @@ void sinterGenericCommand(redisClient *c, robj **setskeys, unsigned long setsnum
}
else
{
}
else
{
/* If we have a target key where to store the resulting set
/* If we have a target key where to store the resulting set
* create this key with an empty set inside */
* 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
/* Iterate all the elements of the first (smallest) set, and test
* the element against all the other sets, if at least one set does
* the element against all the other sets, if at least one set does
* not include the element it is discarded */
* 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
)
{
/* Only take action when all sets contain the member */
robj
*
ele
;
if
(
j
==
setnum
)
{
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
);
if
(
!
dstkey
)
{
if
(
!
dstkey
)
{
addReplyBulk
(
c
,
ele
);
addReplyBulk
(
c
,
ele
);
cardinality
++
;
cardinality
++
;
}
else
{
}
else
{
dictAdd
(
dstset
->
ptr
,
ele
,
NULL
);
setTypeAdd
(
dstset
,
ele
);
incrRefCount
(
ele
);
}
}
}
decrRefCount
(
ele
);
}
}
dict
ReleaseIterator
(
d
i
);
setType
ReleaseIterator
(
s
i
);
if
(
dstkey
)
{
if
(
dstkey
)
{
/* Store the resulting set into the target, if the intersection
/* Store the resulting set into the target, if the intersection
* is not an empty set. */
* is not an empty set. */
dbDelete
(
c
->
db
,
dstkey
);
dbDelete
(
c
->
db
,
dstkey
);
if
(
dictSize
((
dict
*
)
dstset
->
ptr
)
>
0
)
{
if
(
setTypeSize
(
dstset
)
>
0
)
{
dbAdd
(
c
->
db
,
dstkey
,
dstset
);
dbAdd
(
c
->
db
,
dstkey
,
dstset
);
addReplyLongLong
(
c
,
dictSize
((
dict
*
)
dstset
->
ptr
));
addReplyLongLong
(
c
,
setTypeSize
(
dstset
));
}
else
{
}
else
{
decrRefCount
(
dstset
);
decrRefCount
(
dstset
);
addReply
(
c
,
shared
.
czero
);
addReply
(
c
,
shared
.
czero
);
...
@@ -241,7 +402,7 @@ void sinterGenericCommand(redisClient *c, robj **setskeys, unsigned long setsnum
...
@@ -241,7 +402,7 @@ void sinterGenericCommand(redisClient *c, robj **setskeys, unsigned long setsnum
}
else
{
}
else
{
lenobj
->
ptr
=
sdscatprintf
(
sdsempty
(),
"*%lu
\r\n
"
,
cardinality
);
lenobj
->
ptr
=
sdscatprintf
(
sdsempty
(),
"*%lu
\r\n
"
,
cardinality
);
}
}
zfree
(
dv
);
zfree
(
sets
);
}
}
void
sinterCommand
(
redisClient
*
c
)
{
void
sinterCommand
(
redisClient
*
c
)
{
...
@@ -252,85 +413,78 @@ void sinterstoreCommand(redisClient *c) {
...
@@ -252,85 +413,78 @@ void sinterstoreCommand(redisClient *c) {
sinterGenericCommand
(
c
,
c
->
argv
+
2
,
c
->
argc
-
2
,
c
->
argv
[
1
]);
sinterGenericCommand
(
c
,
c
->
argv
+
2
,
c
->
argc
-
2
,
c
->
argv
[
1
]);
}
}
void
sunionDiffGenericCommand
(
redisClient
*
c
,
robj
**
setskeys
,
int
setsnum
,
robj
*
dstkey
,
int
op
)
{
#define REDIS_OP_UNION 0
dict
**
dv
=
zmalloc
(
sizeof
(
dict
*
)
*
setsnum
);
#define REDIS_OP_DIFF 1
dictIterator
*
di
;
#define REDIS_OP_INTER 2
dictEntry
*
de
;
robj
*
dstset
=
NULL
;
int
j
,
cardinality
=
0
;
for
(
j
=
0
;
j
<
setsnum
;
j
++
)
{
void
sunionDiffGenericCommand
(
redisClient
*
c
,
robj
**
setkeys
,
int
setnum
,
robj
*
dstkey
,
int
op
)
{
robj
*
setobj
;
robj
**
sets
=
zmalloc
(
sizeof
(
robj
*
)
*
setnum
);
setTypeIterator
*
si
;
robj
*
ele
,
*
dstset
=
NULL
;
int
j
,
cardinality
=
0
;
setobj
=
dstkey
?
for
(
j
=
0
;
j
<
setnum
;
j
++
)
{
lookupKeyWrite
(
c
->
db
,
setskeys
[
j
])
:
robj
*
setobj
=
dstkey
?
lookupKeyRead
(
c
->
db
,
setskeys
[
j
]);
lookupKeyWrite
(
c
->
db
,
setkeys
[
j
])
:
lookupKeyRead
(
c
->
db
,
setkeys
[
j
]);
if
(
!
setobj
)
{
if
(
!
setobj
)
{
dv
[
j
]
=
NULL
;
sets
[
j
]
=
NULL
;
continue
;
continue
;
}
}
if
(
setobj
->
type
!=
REDIS_SET
)
{
if
(
checkType
(
c
,
setobj
,
REDIS_SET
))
{
zfree
(
dv
);
zfree
(
sets
);
addReply
(
c
,
shared
.
wrongtypeerr
);
return
;
return
;
}
}
dv
[
j
]
=
setobj
->
ptr
;
sets
[
j
]
=
setobj
;
}
}
/* We need a temp set object to store our union. If the dstkey
/* 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
* 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*/
* 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
/* Iterate all the elements of all the sets, add every element a single
* time to the result set */
* time to the result set */
for
(
j
=
0
;
j
<
setsnum
;
j
++
)
{
for
(
j
=
0
;
j
<
setnum
;
j
++
)
{
if
(
op
==
REDIS_OP_DIFF
&&
j
==
0
&&
!
dv
[
j
])
break
;
/* result set is empty */
if
(
op
==
REDIS_OP_DIFF
&&
j
==
0
&&
!
sets
[
j
])
break
;
/* result set is empty */
if
(
!
dv
[
j
])
continue
;
/* non existing keys are like empty sets */
if
(
!
sets
[
j
])
continue
;
/* non existing keys are like empty sets */
di
=
dictGetIterator
(
dv
[
j
]);
while
((
de
=
dictNext
(
di
))
!=
NULL
)
{
si
=
setTypeInitIterator
(
sets
[
j
]);
robj
*
ele
;
while
((
ele
=
setTypeNext
(
si
))
!=
NULL
)
{
/* dictAdd will not add the same element multiple times */
ele
=
dictGetEntryKey
(
de
);
if
(
op
==
REDIS_OP_UNION
||
j
==
0
)
{
if
(
op
==
REDIS_OP_UNION
||
j
==
0
)
{
if
(
dictAdd
(
dstset
->
ptr
,
ele
,
NULL
)
==
DICT_OK
)
{
if
(
setTypeAdd
(
dstset
,
ele
))
{
incrRefCount
(
ele
);
cardinality
++
;
cardinality
++
;
}
}
}
else
if
(
op
==
REDIS_OP_DIFF
)
{
}
else
if
(
op
==
REDIS_OP_DIFF
)
{
if
(
dictDelete
(
dstset
->
ptr
,
ele
)
==
DICT_OK
)
{
if
(
setTypeRemove
(
dstset
,
ele
)
)
{
cardinality
--
;
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
;
if
(
op
==
REDIS_OP_DIFF
&&
cardinality
==
0
)
break
;
}
}
/* Output the content of the resulting set, if not in STORE mode */
/* Output the content of the resulting set, if not in STORE mode */
if
(
!
dstkey
)
{
if
(
!
dstkey
)
{
addReplySds
(
c
,
sdscatprintf
(
sdsempty
(),
"*%d
\r\n
"
,
cardinality
));
addReplySds
(
c
,
sdscatprintf
(
sdsempty
(),
"*%d
\r\n
"
,
cardinality
));
di
=
dictGetIterator
(
dstset
->
ptr
);
si
=
setTypeInitIterator
(
dstset
);
while
((
de
=
dictNext
(
di
))
!=
NULL
)
{
while
((
ele
=
setTypeNext
(
si
))
!=
NULL
)
{
robj
*
ele
;
ele
=
dictGetEntryKey
(
de
);
addReplyBulk
(
c
,
ele
);
addReplyBulk
(
c
,
ele
);
decrRefCount
(
ele
);
}
}
dict
ReleaseIterator
(
d
i
);
setType
ReleaseIterator
(
s
i
);
decrRefCount
(
dstset
);
decrRefCount
(
dstset
);
}
else
{
}
else
{
/* If we have a target key where to store the resulting set
/* If we have a target key where to store the resulting set
* create this key with the result set inside */
* create this key with the result set inside */
dbDelete
(
c
->
db
,
dstkey
);
dbDelete
(
c
->
db
,
dstkey
);
if
(
dictSize
((
dict
*
)
dstset
->
ptr
)
>
0
)
{
if
(
setTypeSize
(
dstset
)
>
0
)
{
dbAdd
(
c
->
db
,
dstkey
,
dstset
);
dbAdd
(
c
->
db
,
dstkey
,
dstset
);
addReplyLongLong
(
c
,
dictSize
((
dict
*
)
dstset
->
ptr
));
addReplyLongLong
(
c
,
setTypeSize
(
dstset
));
}
else
{
}
else
{
decrRefCount
(
dstset
);
decrRefCount
(
dstset
);
addReply
(
c
,
shared
.
czero
);
addReply
(
c
,
shared
.
czero
);
...
@@ -338,7 +492,7 @@ void sunionDiffGenericCommand(redisClient *c, robj **setskeys, int setsnum, robj
...
@@ -338,7 +492,7 @@ void sunionDiffGenericCommand(redisClient *c, robj **setskeys, int setsnum, robj
touchWatchedKey
(
c
->
db
,
dstkey
);
touchWatchedKey
(
c
->
db
,
dstkey
);
server
.
dirty
++
;
server
.
dirty
++
;
}
}
zfree
(
dv
);
zfree
(
sets
);
}
}
void
sunionCommand
(
redisClient
*
c
)
{
void
sunionCommand
(
redisClient
*
c
)
{
...
...
tests/support/server.tcl
View file @
acc75bfd
...
@@ -230,7 +230,11 @@ proc start_server {options {code undefined}} {
...
@@ -230,7 +230,11 @@ proc start_server {options {code undefined}} {
# execute provided block
# execute provided block
set curnum $::testnum
set curnum $::testnum
catch
{
uplevel 1 $code
}
err
if
{
!
[
catch
{
uplevel 1 $code
}
err
]}
{
# zero exit status is good
unset err
}
if
{
$curnum
== $::testnum
}
{
if
{
$curnum
== $::testnum
}
{
# don't check for leaks when no tests were executed
# don't check for leaks when no tests were executed
dict set srv
"skipleaks"
1
dict set srv
"skipleaks"
1
...
@@ -241,6 +245,7 @@ proc start_server {options {code undefined}} {
...
@@ -241,6 +245,7 @@ proc start_server {options {code undefined}} {
# allow an exception to bubble up the call chain but still kill this
# 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
# server, because we want to reuse the ports when the tests are re-run
if
{[
info exists err
]}
{
if
{
$err
eq
"exception"
}
{
if
{
$err
eq
"exception"
}
{
puts
[
format
"Logged warnings (pid %d):"
[
dict get $srv
"pid"
]]
puts
[
format
"Logged warnings (pid %d):"
[
dict get $srv
"pid"
]]
set warnings
[
warnings_from_file
[
dict get $srv
"stdout"
]]
set warnings
[
warnings_from_file
[
dict get $srv
"stdout"
]]
...
@@ -258,6 +263,7 @@ proc start_server {options {code undefined}} {
...
@@ -258,6 +263,7 @@ proc start_server {options {code undefined}} {
puts $err
puts $err
exit 1
exit 1
}
}
}
set ::tags
[
lrange $::tags 0 end-
[
llength $tags
]]
set ::tags
[
lrange $::tags 0 end-
[
llength $tags
]]
kill_server $srv
kill_server $srv
...
...
tests/unit/sort.tcl
View file @
acc75bfd
start_server
{
tags
{
"sort"
}}
{
start_server
{
test
{
SORT ALPHA against integer encoded strings
}
{
tags
{
"sort"
}
r del mylist
overrides
{
r lpush mylist 2
"list-max-ziplist-value"
16
r lpush mylist 1
"list-max-ziplist-entries"
32
r lpush mylist 3
"set-max-intset-entries"
32
r lpush mylist 10
}
r sort mylist alpha
}
{
}
{
1 10 2 3
}
proc create_random_dataset
{
num cmd
}
{
tags
{
"slow"
}
{
set res
{}
test
{
Create a random list and a random set
}
{
set tosort
{}
set tosort
{}
set result
{}
array set seenrand
{}
array set seenrand
{}
for
{
set i 0
}
{
$i
< 10000
}
{
incr i
}
{
r del tosort
while 1
{
for
{
set i 0
}
{
$i
< $num
}
{
incr i
}
{
# Make sure all the weights are different because
# Make sure all the weights are different because
# Redis does not use a stable sort but Tcl does.
# Redis does not use a stable sort but Tcl does.
while 1
{
randpath
{
randpath
{
set rint
[
expr int
(
rand
()
*1000000
)]
set rint
[
expr int
(
rand
()
*1000000
)]
}
{
}
{
...
@@ -25,72 +23,120 @@ start_server {tags {"sort"}} {
...
@@ -25,72 +23,120 @@ start_server {tags {"sort"}} {
if
{
!
[
info exists seenrand
(
$rint
)]}
break
if
{
!
[
info exists seenrand
(
$rint
)]}
break
}
}
set seenrand
(
$rint
)
x
set seenrand
(
$rint
)
x
r lpush tosort $i
r $cmd tosort $i
r sadd tosort-set $i
r set weight_$i $rint
r set weight_$i $rint
r hset wobj_$i weight $rint
r hset wobj_$i weight $rint
lappend tosort
[
list $i $rint
]
lappend tosort
[
list $i $rint
]
}
}
set sorted
[
lsort -index 1 -real $tosort
]
set sorted
[
lsort -index 1 -real $tosort
]
for
{
set i 0
}
{
$i
<
10000
}
{
incr i
}
{
for
{
set i 0
}
{
$i
<
$num
}
{
incr i
}
{
lappend res
[
lindex $sorted $i 0
]
lappend res
ult
[
lindex $sorted $i 0
]
}
}
format
{}
set _ $result
}
{}
}
foreach
{
num cmd enc title
}
{
16 lpush ziplist
"Ziplist"
64 lpush linkedlist
"Linked list"
16 sadd intset
"Intset"
64 sadd hashtable
"Hash table"
}
{
set result
[
create_random_dataset $num $cmd
]
assert_encoding $enc tosort
test
{
SORT with BY against the newly created list
}
{
test
"
$title:
SORT BY key"
{
r sort tosort
{
BY weight_*
}
assert_equal $result
[
r sort tosort
{
BY weight_*
}
]
}
$res
}
test
{
SORT with BY
(
hash field
)
against the newly created list
}
{
test
"
$title:
SORT BY hash field"
{
r sort tosort
{
BY wobj_*->weight
}
assert_equal $result
[
r sort tosort
{
BY wobj_*->weight
}]
}
$res
}
}
test
{
SORT with GET
(
key+hash
)
with sanity check of each element
(
list
)}
{
set result
[
create_random_dataset 16 lpush
]
set err
{}
test
"SORT GET #"
{
assert_equal
[
lsort -integer $result
]
[
r sort tosort GET #
]
}
test
"SORT GET <const>"
{
r del foo
set res
[
r sort tosort GET foo
]
assert_equal 16
[
llength $res
]
foreach item $res
{
assert_equal
{}
$item
}
}
test
"SORT GET (key and hash) with sanity check"
{
set l1
[
r sort tosort GET # GET weight_*
]
set l1
[
r sort tosort GET # GET weight_*
]
set l2
[
r sort tosort GET # GET wobj_*->weight
]
set l2
[
r sort tosort GET # GET wobj_*->weight
]
foreach
{
id1 w1
}
$l1
{
id2 w2
}
$l2
{
foreach
{
id1 w1
}
$l1
{
id2 w2
}
$l2
{
set realweight
[
r get weight_$id1
]
assert_equal $id1 $id2
if
{
$id1
!= $id2
}
{
assert_equal $w1
[
r get weight_$id1
]
set err
"ID mismatch
$id1
!=
$id2
"
assert_equal $w2
[
r get weight_$id1
]
break
}
}
if
{
$realweight
!= $w1 || $realweight != $w2
}
{
set err
"Weights mismatch! w1:
$w1
w2:
$w2
real:
$realweight
"
break
}
}
test
"SORT BY key STORE"
{
r sort tosort
{
BY weight_*
}
store sort-res
assert_equal $result
[
r lrange sort-res 0 -1
]
assert_equal 16
[
r llen sort-res
]
assert_encoding ziplist sort-res
}
}
set _ $err
}
{}
test
{
SORT with BY, but against the newly created set
}
{
test
"SORT BY hash field STORE"
{
r sort tosort-set
{
BY weight_*
}
r sort tosort
{
BY wobj_*->weight
}
store sort-res
}
$res
assert_equal $result
[
r lrange sort-res 0 -1
]
assert_equal 16
[
r llen sort-res
]
assert_encoding ziplist sort-res
}
test
{
SORT
with BY
(
hash field
)
, but against the newly created set
}
{
test
"
SORT
DESC"
{
r sort tosort-set
{
BY wobj_*->weight
}
assert_equal
[
lsort -decreasing -integer $result
]
[
r sort tosort
{
DESC
}]
}
$res
}
test
{
SORT with BY and STORE against the newly created list
}
{
test
"SORT ALPHA against integer encoded strings"
{
r sort tosort
{
BY weight_*
}
store sort-res
r del mylist
r lrange sort-res 0 -1
r lpush mylist 2
}
$res
r lpush mylist 1
r lpush mylist 3
r lpush mylist 10
r sort mylist alpha
}
{
1 10 2 3
}
test
{
SORT with BY
(
hash field
)
and STORE against the newly created list
}
{
test
"SORT sorted set"
{
r sort tosort
{
BY wobj_*->weight
}
store sort-res
r del zset
r lrange sort-res 0 -1
r zadd zset 1 a
}
$res
r zadd zset 5 b
r zadd zset 2 c
r zadd zset 10 d
r zadd zset 3 e
r sort zset alpha desc
}
{
e d c b a
}
test
"SORT sorted set: +inf and -inf handling"
{
r del zset
r zadd zset -100 a
r zadd zset 200 b
r zadd zset -300 c
r zadd zset 1000000 d
r zadd zset +inf max
r zadd zset -inf min
r zrange zset 0 -1
}
{
min c a b d max
}
test
{
SORT direct, numeric, against the newly created list
}
{
test
"SORT regression for issue #19, sorting floats"
{
r sort tosort
r flushdb
}
[
lsort -integer $res
]
set floats
{
1.1 5.10 3.10 7.44 2.1 5.75 6.12 0.25 1.15
}
foreach x $floats
{
r lpush mylist $x
}
assert_equal
[
lsort -real $floats
]
[
r sort mylist
]
}
t
est
{
SORT decreasing sort
}
{
t
ags
{
"slow"
}
{
r sort tosort
{
DESC
}
set num 100
}
[
lsort -decreasing -integer $res
]
set res
[
create_random_dataset $num lpush
]
test
{
SORT speed,
sorting 10000
element
s
list
using BY
, 100 times
}
{
test
"
SORT speed,
$num
element list
BY key
, 100 times
"
{
set start
[
clock clicks -milliseconds
]
set start
[
clock clicks -milliseconds
]
for
{
set i 0
}
{
$i
< 100
}
{
incr i
}
{
for
{
set i 0
}
{
$i
< 100
}
{
incr i
}
{
set sorted
[
r sort tosort
{
BY weight_* LIMIT 0 10
}]
set sorted
[
r sort tosort
{
BY weight_* LIMIT 0 10
}]
...
@@ -98,10 +144,9 @@ start_server {tags {"sort"}} {
...
@@ -98,10 +144,9 @@ start_server {tags {"sort"}} {
set elapsed
[
expr
[
clock clicks -milliseconds
]
-$start
]
set elapsed
[
expr
[
clock clicks -milliseconds
]
-$start
]
puts -nonewline
"
\n
Average time to sort:
[
expr double
(
$elapsed
)
/100
]
milliseconds "
puts -nonewline
"
\n
Average time to sort:
[
expr double
(
$elapsed
)
/100
]
milliseconds "
flush stdout
flush stdout
format
{}
}
}
{}
test
{
SORT speed,
as above but against hash field
}
{
test
"
SORT speed,
$num
element list BY hash field, 100 times"
{
set start
[
clock clicks -milliseconds
]
set start
[
clock clicks -milliseconds
]
for
{
set i 0
}
{
$i
< 100
}
{
incr i
}
{
for
{
set i 0
}
{
$i
< 100
}
{
incr i
}
{
set sorted
[
r sort tosort
{
BY wobj_*->weight LIMIT 0 10
}]
set sorted
[
r sort tosort
{
BY wobj_*->weight LIMIT 0 10
}]
...
@@ -109,10 +154,9 @@ start_server {tags {"sort"}} {
...
@@ -109,10 +154,9 @@ start_server {tags {"sort"}} {
set elapsed
[
expr
[
clock clicks -milliseconds
]
-$start
]
set elapsed
[
expr
[
clock clicks -milliseconds
]
-$start
]
puts -nonewline
"
\n
Average time to sort:
[
expr double
(
$elapsed
)
/100
]
milliseconds "
puts -nonewline
"
\n
Average time to sort:
[
expr double
(
$elapsed
)
/100
]
milliseconds "
flush stdout
flush stdout
format
{}
}
}
{}
test
{
SORT speed,
sorting 10000
element
s
list directly, 100 times
}
{
test
"
SORT speed,
$num
element list directly, 100 times
"
{
set start
[
clock clicks -milliseconds
]
set start
[
clock clicks -milliseconds
]
for
{
set i 0
}
{
$i
< 100
}
{
incr i
}
{
for
{
set i 0
}
{
$i
< 100
}
{
incr i
}
{
set sorted
[
r sort tosort
{
LIMIT 0 10
}]
set sorted
[
r sort tosort
{
LIMIT 0 10
}]
...
@@ -120,10 +164,9 @@ start_server {tags {"sort"}} {
...
@@ -120,10 +164,9 @@ start_server {tags {"sort"}} {
set elapsed
[
expr
[
clock clicks -milliseconds
]
-$start
]
set elapsed
[
expr
[
clock clicks -milliseconds
]
-$start
]
puts -nonewline
"
\n
Average time to sort:
[
expr double
(
$elapsed
)
/100
]
milliseconds "
puts -nonewline
"
\n
Average time to sort:
[
expr double
(
$elapsed
)
/100
]
milliseconds "
flush stdout
flush stdout
format
{}
}
}
{}
test
{
SORT speed,
pseudo-sorting 10000
element
s
list
,
BY <const>, 100 times
}
{
test
"
SORT speed,
$num
element list BY <const>, 100 times
"
{
set start
[
clock clicks -milliseconds
]
set start
[
clock clicks -milliseconds
]
for
{
set i 0
}
{
$i
< 100
}
{
incr i
}
{
for
{
set i 0
}
{
$i
< 100
}
{
incr i
}
{
set sorted
[
r sort tosort
{
BY nokey LIMIT 0 10
}]
set sorted
[
r sort tosort
{
BY nokey LIMIT 0 10
}]
...
@@ -131,49 +174,6 @@ start_server {tags {"sort"}} {
...
@@ -131,49 +174,6 @@ start_server {tags {"sort"}} {
set elapsed
[
expr
[
clock clicks -milliseconds
]
-$start
]
set elapsed
[
expr
[
clock clicks -milliseconds
]
-$start
]
puts -nonewline
"
\n
Average time to sort:
[
expr double
(
$elapsed
)
/100
]
milliseconds "
puts -nonewline
"
\n
Average time to sort:
[
expr double
(
$elapsed
)
/100
]
milliseconds "
flush stdout
flush stdout
format
{}
}
{}
}
}
test
{
SORT regression for issue #19, sorting floats
}
{
r flushdb
foreach x
{
1.1 5.10 3.10 7.44 2.1 5.75 6.12 0.25 1.15
}
{
r lpush mylist $x
}
}
r sort mylist
}
[
lsort -real
{
1.1 5.10 3.10 7.44 2.1 5.75 6.12 0.25 1.15
}]
test
{
SORT with GET #
}
{
r del mylist
r lpush mylist 1
r lpush mylist 2
r lpush mylist 3
r mset weight_1 10 weight_2 5 weight_3 30
r sort mylist BY weight_* GET #
}
{
2 1 3
}
test
{
SORT with constant GET
}
{
r sort mylist GET foo
}
{{}
{}
{}}
test
{
SORT against sorted sets
}
{
r del zset
r zadd zset 1 a
r zadd zset 5 b
r zadd zset 2 c
r zadd zset 10 d
r zadd zset 3 e
r sort zset alpha desc
}
{
e d c b a
}
test
{
Sorted sets +inf and -inf handling
}
{
r del zset
r zadd zset -100 a
r zadd zset 200 b
r zadd zset -300 c
r zadd zset 1000000 d
r zadd zset +inf max
r zadd zset -inf min
r zrange zset 0 -1
}
{
min c a b d max
}
}
}
tests/unit/type/set.tcl
View file @
acc75bfd
start_server
{
tags
{
"set"
}}
{
start_server
{
test
{
SADD, SCARD, SISMEMBER, SMEMBERS basics
}
{
tags
{
"set"
}
r sadd myset foo
overrides
{
r sadd myset bar
"set-max-intset-entries"
512
list
[
r scard myset
]
[
r sismember myset foo
]
\
}
[
r sismember myset bar
]
[
r sismember myset bla
]
\
}
{
[
lsort
[
r smembers myset
]]
proc create_set
{
key entries
}
{
}
{
2 1 1 0
{
bar foo
}}
r del $key
foreach entry $entries
{
r sadd $key $entry
}
test
{
SADD adding the same element multiple times
}
{
}
r sadd myset foo
r sadd myset foo
test
{
SADD, SCARD, SISMEMBER, SMEMBERS basics - regular set
}
{
r sadd myset foo
create_set myset
{
foo
}
r scard myset
assert_encoding hashtable myset
}
{
2
}
assert_equal 1
[
r sadd myset bar
]
assert_equal 0
[
r sadd myset bar
]
assert_equal 2
[
r scard myset
]
assert_equal 1
[
r sismember myset foo
]
assert_equal 1
[
r sismember myset bar
]
assert_equal 0
[
r sismember myset bla
]
assert_equal
{
bar foo
}
[
lsort
[
r smembers myset
]]
}
test
{
SADD, SCARD, SISMEMBER, SMEMBERS basics - intset
}
{
create_set myset
{
17
}
assert_encoding intset myset
assert_equal 1
[
r sadd myset 16
]
assert_equal 0
[
r sadd myset 16
]
assert_equal 2
[
r scard myset
]
assert_equal 1
[
r sismember myset 16
]
assert_equal 1
[
r sismember myset 17
]
assert_equal 0
[
r sismember myset 18
]
assert_equal
{
16 17
}
[
lsort
[
r smembers myset
]]
}
test
{
SADD against non set
}
{
test
{
SADD against non set
}
{
r lpush mylist foo
r lpush mylist foo
catch
{
r sadd mylist bar
}
err
assert_error ERR*kind*
{
r sadd mylist bar
}
format $err
}
}
{
ERR*kind*
}
test
"SADD a non-integer against an intset"
{
test
{
SREM basics
}
{
create_set myset
{
1 2 3
}
r sadd myset ciao
assert_encoding intset myset
r srem myset foo
assert_equal 1
[
r sadd myset a
]
lsort
[
r smembers myset
]
assert_encoding hashtable myset
}
{
bar ciao
}
}
test
{
Mass SADD and SINTER with two sets
}
{
test
"SADD an integer larger than 64 bits"
{
for
{
set i 0
}
{
$i
< 1000
}
{
incr i
}
{
create_set myset
{
213244124402402314402033402
}
assert_encoding hashtable myset
assert_equal 1
[
r sismember myset 213244124402402314402033402
]
}
test
"SADD overflows the maximum allowed integers in an intset"
{
r del myset
for
{
set i 0
}
{
$i
< 512
}
{
incr i
}
{
r sadd myset $i
}
assert_encoding intset myset
assert_equal 1
[
r sadd myset 512
]
assert_encoding hashtable myset
}
test
"Set encoding after DEBUG RELOAD"
{
r del myintset myhashset mylargeintset
for
{
set i 0
}
{
$i
< 100
}
{
incr i
}
{
r sadd myintset $i
}
for
{
set i 0
}
{
$i
< 1280
}
{
incr i
}
{
r sadd mylargeintset $i
}
for
{
set i 0
}
{
$i
< 256
}
{
incr i
}
{
r sadd myhashset
[
format
"i%03d"
$i
]
}
assert_encoding intset myintset
assert_encoding hashtable mylargeintset
assert_encoding hashtable myhashset
r debug reload
assert_encoding intset myintset
assert_encoding hashtable mylargeintset
assert_encoding hashtable myhashset
}
test
{
SREM basics - regular set
}
{
create_set myset
{
foo bar ciao
}
assert_encoding hashtable myset
assert_equal 0
[
r srem myset qux
]
assert_equal 1
[
r srem myset foo
]
assert_equal
{
bar ciao
}
[
lsort
[
r smembers myset
]]
}
test
{
SREM basics - intset
}
{
create_set myset
{
3 4 5
}
assert_encoding intset myset
assert_equal 0
[
r srem myset 6
]
assert_equal 1
[
r srem myset 4
]
assert_equal
{
3 5
}
[
lsort
[
r smembers myset
]]
}
foreach
{
type
}
{
hashtable intset
}
{
for
{
set i 1
}
{
$i
<= 5
}
{
incr i
}
{
r del
[
format
"set%d"
$i
]
}
for
{
set i 0
}
{
$i
< 200
}
{
incr i
}
{
r sadd set1 $i
r sadd set1 $i
r sadd set2
[
expr $i+995
]
r sadd set2
[
expr $i+195
]
}
foreach i
{
199 195 1000 2000
}
{
r sadd set3 $i
}
for
{
set i 5
}
{
$i
< 200
}
{
incr i
}
{
r sadd set4 $i
}
}
lsort
[
r sinter set1 set2
]
r sadd set5 0
}
{
995 996 997 998 999
}
test
{
SUNION with two sets
}
{
# it is possible that a hashtable encoded only contains integers,
lsort
[
r sunion set1 set2
]
# because it is converted from an intset to a hashtable when a
}
[
lsort -uniq
"
[
r smembers set1
]
[
r smembers set2
]
"
]
# non-integer element is added and then removed.
if
{
$type
eq
"hashtable"
}
{
for
{
set i 1
}
{
$i
<= 5
}
{
incr i
}
{
r sadd
[
format
"set%d"
$i
]
foo
r srem
[
format
"set%d"
$i
]
foo
}
}
test
{
SINTERSTORE with two sets
}
{
test
"Generated sets must be encoded as
$type
"
{
for
{
set i 1
}
{
$i
<= 5
}
{
incr i
}
{
assert_encoding $type
[
format
"set%d"
$i
]
}
}
test
"SINTER with two sets -
$type
"
{
assert_equal
{
195 196 197 198 199
}
[
lsort
[
r sinter set1 set2
]]
}
test
"SINTERSTORE with two sets -
$type
"
{
r sinterstore setres set1 set2
r sinterstore setres set1 set2
lsort
[
r smembers setres
]
assert_encoding intset setres
}
{
995 996 997 998 999
}
assert_equal
{
195 196 197 198 199
}
[
lsort
[
r smembers setres
]]
}
test
{
SINTERSTORE with two sets, after a DEBUG RELOAD
}
{
test
"
SINTERSTORE with two sets, after a DEBUG RELOAD
-
$type
"
{
r debug reload
r debug reload
r sinterstore setres set1 set2
r sinterstore setres set1 set2
lsort
[
r smembers setres
]
assert_encoding intset setres
}
{
995 996 997 998 999
}
assert_equal
{
195 196 197 198 199
}
[
lsort
[
r smembers setres
]]
}
test
{
SUNIONSTORE with two sets
}
{
test
"SUNION with two sets -
$type
"
{
set expected
[
lsort -uniq
"
[
r smembers set1
]
[
r smembers set2
]
"
]
assert_equal $expected
[
lsort
[
r sunion set1 set2
]]
}
test
"SUNIONSTORE with two sets -
$type
"
{
r sunionstore setres set1 set2
r sunionstore setres set1 set2
lsort
[
r smembers setres
]
assert_encoding intset setres
}
[
lsort -uniq
"
[
r smembers set1
]
[
r smembers set2
]
"
]
set expected
[
lsort -uniq
"
[
r smembers set1
]
[
r smembers set2
]
"
]
assert_equal $expected
[
lsort
[
r smembers setres
]]
}
test
{
SUNIONSTORE against non existing keys
}
{
test
"SINTER against three sets -
$type
"
{
r set setres xxx
assert_equal
{
195 199
}
[
lsort
[
r sinter set1 set2 set3
]]
list
[
r sunionstore setres foo111 bar222
]
[
r exists xxx
]
}
}
{
0 0
}
test
"SINTERSTORE with three sets -
$type
"
{
test
{
SINTER against three sets
}
{
r sadd set3 999
r sadd set3 995
r sadd set3 1000
r sadd set3 2000
lsort
[
r sinter set1 set2 set3
]
}
{
995 999
}
test
{
SINTERSTORE with three sets
}
{
r sinterstore setres set1 set2 set3
r sinterstore setres set1 set2 set3
lsort
[
r smembers setres
]
assert_equal
{
195 199
}
[
r smembers setres
]
}
{
995 999
}
}
test
{
SUNION with non existing keys
}
{
test
"SUNION with non existing keys -
$type
"
{
lsort
[
r sunion nokey1 set1 set2 nokey2
]
set expected
[
lsort -uniq
"
[
r smembers set1
]
[
r smembers set2
]
"
]
}
[
lsort -uniq
"
[
r smembers set1
]
[
r smembers set2
]
"
]
assert_equal $expected
[
lsort
[
r sunion nokey1 set1 set2 nokey2
]]
}
test
{
SDIFF with two sets
}
{
test
"SDIFF with two sets -
$type
"
{
for
{
set i 5
}
{
$i
< 1000
}
{
incr i
}
{
assert_equal
{
0 1 2 3 4
}
[
lsort
[
r sdiff set1 set4
]]
r sadd set4 $i
}
}
lsort
[
r sdiff set1 set4
]
}
{
0 1 2 3 4
}
test
{
SDIFF with three sets
}
{
test
"SDIFF with three sets -
$type
"
{
r sadd set5 0
assert_equal
{
1 2 3 4
}
[
lsort
[
r sdiff set1 set4 set5
]]
lsort
[
r sdiff set1 set4 set5
]
}
}
{
1 2 3 4
}
test
{
SDIFFSTORE with three sets
}
{
test
"SDIFFSTORE with three sets -
$type
"
{
r sdiffstore sres set1 set4 set5
r sdiffstore setres set1 set4 set5
lsort
[
r smembers sres
]
assert_encoding intset setres
}
{
1 2 3 4
}
assert_equal
{
1 2 3 4
}
[
lsort
[
r smembers setres
]]
}
}
test
{
SPOP basics
}
{
test
"SINTER against non-set should throw error"
{
r del myset
r set key1 x
r sadd myset 1
assert_error
"ERR*wrong kind*"
{
r sinter key1 noset
}
r sadd myset 2
}
r sadd myset 3
list
[
lsort
[
list
[
r spop myset
]
[
r spop myset
]
[
r spop myset
]]]
[
r scard myset
]
}
{{
1 2 3
}
0
}
test
{
SRANDMEMBER
}
{
test
"SUNION against non-set should throw error"
{
r del myset
r set key1 x
r sadd myset a
assert_error
"ERR*wrong kind*"
{
r sunion key1 noset
}
r sadd myset b
}
r sadd myset c
test
"SINTERSTORE against non existing keys should delete dstkey"
{
r set setres xxx
assert_equal 0
[
r sinterstore setres foo111 bar222
]
assert_equal 0
[
r exists setres
]
}
test
"SUNIONSTORE against non existing keys should delete dstkey"
{
r set setres xxx
assert_equal 0
[
r sunionstore setres foo111 bar222
]
assert_equal 0
[
r exists setres
]
}
foreach
{
type contents
}
{
hashtable
{
a b c
}
intset
{
1 2 3
}}
{
test
"SPOP basics -
$type
"
{
create_set myset $contents
assert_encoding $type myset
assert_equal $contents
[
lsort
[
list
[
r spop myset
]
[
r spop myset
]
[
r spop myset
]]]
assert_equal 0
[
r scard myset
]
}
test
"SRANDMEMBER -
$type
"
{
create_set myset $contents
unset -nocomplain myset
unset -nocomplain myset
array set myset
{}
array set myset
{}
for
{
set i 0
}
{
$i
< 100
}
{
incr i
}
{
for
{
set i 0
}
{
$i
< 100
}
{
incr i
}
{
set myset
([
r srandmember myset
])
1
set myset
([
r srandmember myset
])
1
}
}
lsort
[
array names myset
]
assert_equal $contents
[
lsort
[
array names myset
]]
}
{
a b c
}
}
}
test
{
SMOVE basics
}
{
r sadd myset1 a
proc setup_move
{}
{
r sadd myset1 b
r del myset3 myset4
r sadd myset1 c
create_set myset1
{
1 a b
}
r sadd myset2 x
create_set myset2
{
2 3 4
}
r sadd myset2 y
assert_encoding hashtable myset1
r sadd myset2 z
assert_encoding intset myset2
r smove myset1 myset2 a
}
list
[
lsort
[
r smembers myset2
]]
[
lsort
[
r smembers myset1
]]
}
{{
a x y z
}
{
b c
}}
test
"SMOVE basics - from regular set to intset"
{
# move a non-integer element to an intset should convert encoding
test
{
SMOVE non existing key
}
{
setup_move
list
[
r smove myset1 myset2 foo
]
[
lsort
[
r smembers myset2
]]
[
lsort
[
r smembers myset1
]]
assert_equal 1
[
r smove myset1 myset2 a
]
}
{
0
{
a x y z
}
{
b c
}}
assert_equal
{
1 b
}
[
lsort
[
r smembers myset1
]]
assert_equal
{
2 3 4 a
}
[
lsort
[
r smembers myset2
]]
test
{
SMOVE non existing src set
}
{
assert_encoding hashtable myset2
list
[
r smove noset myset2 foo
]
[
lsort
[
r smembers myset2
]]
}
{
0
{
a x y z
}}
# move an integer element should not convert the encoding
setup_move
test
{
SMOVE non existing dst set
}
{
assert_equal 1
[
r smove myset1 myset2 1
]
list
[
r smove myset2 myset3 y
]
[
lsort
[
r smembers myset2
]]
[
lsort
[
r smembers myset3
]]
assert_equal
{
a b
}
[
lsort
[
r smembers myset1
]]
}
{
1
{
a x z
}
y
}
assert_equal
{
1 2 3 4
}
[
lsort
[
r smembers myset2
]]
assert_encoding intset myset2
test
{
SMOVE wrong src key type
}
{
}
test
"SMOVE basics - from intset to regular set"
{
setup_move
assert_equal 1
[
r smove myset2 myset1 2
]
assert_equal
{
1 2 a b
}
[
lsort
[
r smembers myset1
]]
assert_equal
{
3 4
}
[
lsort
[
r smembers myset2
]]
}
test
"SMOVE non existing key"
{
setup_move
assert_equal 0
[
r smove myset1 myset2 foo
]
assert_equal
{
1 a b
}
[
lsort
[
r smembers myset1
]]
assert_equal
{
2 3 4
}
[
lsort
[
r smembers myset2
]]
}
test
"SMOVE non existing src set"
{
setup_move
assert_equal 0
[
r smove noset myset2 foo
]
assert_equal
{
2 3 4
}
[
lsort
[
r smembers myset2
]]
}
test
"SMOVE from regular set to non existing destination set"
{
setup_move
assert_equal 1
[
r smove myset1 myset3 a
]
assert_equal
{
1 b
}
[
lsort
[
r smembers myset1
]]
assert_equal
{
a
}
[
lsort
[
r smembers myset3
]]
assert_encoding hashtable myset3
}
test
"SMOVE from intset to non existing destination set"
{
setup_move
assert_equal 1
[
r smove myset2 myset3 2
]
assert_equal
{
3 4
}
[
lsort
[
r smembers myset2
]]
assert_equal
{
2
}
[
lsort
[
r smembers myset3
]]
assert_encoding intset myset3
}
test
"SMOVE wrong src key type"
{
r set x 10
r set x 10
catch
{
r smove x myset2 foo
}
err
assert_error
"ERR*wrong kind*"
{
r smove x myset2 foo
}
format $err
}
}
{
ERR*
}
test
{
SMOVE wrong dst key type
}
{
test
"
SMOVE wrong dst key type
"
{
r set x 10
r set x 10
catch
{
r smove myset2 x foo
}
err
assert_error
"ERR*wrong kind*"
{
r smove myset2 x foo
}
format $err
}
}
{
ERR*
}
}
}
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