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
28a250d9
Commit
28a250d9
authored
May 29, 2015
by
antirez
Browse files
Merge branch 'zaddnx' into unstable
parents
a391c363
39b49bca
Changes
2
Show whitespace changes
Inline
Side-by-side
src/t_zset.c
View file @
28a250d9
...
@@ -1171,33 +1171,82 @@ void zsetConvert(robj *zobj, int encoding) {
...
@@ -1171,33 +1171,82 @@ void zsetConvert(robj *zobj, int encoding) {
*----------------------------------------------------------------------------*/
*----------------------------------------------------------------------------*/
/* This generic command implements both ZADD and ZINCRBY. */
/* This generic command implements both ZADD and ZINCRBY. */
void
zaddGenericCommand
(
redisClient
*
c
,
int
incr
)
{
#define ZADD_NONE 0
#define ZADD_INCR (1<<0)
/* Increment the score instead of setting it. */
#define ZADD_NX (1<<1)
/* Don't touch elements not already existing. */
#define ZADD_XX (1<<2)
/* Only touch elements already exisitng. */
#define ZADD_CH (1<<3)
/* Return num of elements added or updated. */
void
zaddGenericCommand
(
redisClient
*
c
,
int
flags
)
{
static
char
*
nanerr
=
"resulting score is not a number (NaN)"
;
static
char
*
nanerr
=
"resulting score is not a number (NaN)"
;
robj
*
key
=
c
->
argv
[
1
];
robj
*
key
=
c
->
argv
[
1
];
robj
*
ele
;
robj
*
ele
;
robj
*
zobj
;
robj
*
zobj
;
robj
*
curobj
;
robj
*
curobj
;
double
score
=
0
,
*
scores
=
NULL
,
curscore
=
0
.
0
;
double
score
=
0
,
*
scores
=
NULL
,
curscore
=
0
.
0
;
int
j
,
elements
=
(
c
->
argc
-
2
)
/
2
;
int
j
,
elements
;
int
added
=
0
,
updated
=
0
;
int
scoreidx
=
0
;
/* The following vars are used in order to track what the command actually
if
(
c
->
argc
%
2
)
{
* did during the execution, to reply to the client and to trigger the
* notification of keyspace change. */
int
added
=
0
;
/* Number of new elements added. */
int
updated
=
0
;
/* Number of elements with updated score. */
int
processed
=
0
;
/* Number of elements processed, may remain zero with
options like XX. */
/* Parse options. At the end 'scoreidx' is set to the argument position
* of the score of the first score-element pair. */
scoreidx
=
2
;
while
(
scoreidx
<
c
->
argc
)
{
char
*
opt
=
c
->
argv
[
scoreidx
]
->
ptr
;
if
(
!
strcasecmp
(
opt
,
"nx"
))
flags
|=
ZADD_NX
;
else
if
(
!
strcasecmp
(
opt
,
"xx"
))
flags
|=
ZADD_XX
;
else
if
(
!
strcasecmp
(
opt
,
"ch"
))
flags
|=
ZADD_CH
;
else
if
(
!
strcasecmp
(
opt
,
"incr"
))
flags
|=
ZADD_INCR
;
else
break
;
scoreidx
++
;
}
/* Turn options into simple to check vars. */
int
incr
=
(
flags
&
ZADD_INCR
)
!=
0
;
int
nx
=
(
flags
&
ZADD_NX
)
!=
0
;
int
xx
=
(
flags
&
ZADD_XX
)
!=
0
;
int
ch
=
(
flags
&
ZADD_CH
)
!=
0
;
/* After the options, we expect to have an even number of args, since
* we expect any number of score-element pairs. */
elements
=
c
->
argc
-
scoreidx
;
if
(
elements
%
2
)
{
addReply
(
c
,
shared
.
syntaxerr
);
addReply
(
c
,
shared
.
syntaxerr
);
return
;
return
;
}
}
elements
/=
2
;
/* Now this holds the number of score-element pairs. */
/* Check for incompatible options. */
if
(
nx
&&
xx
)
{
addReplyError
(
c
,
"XX and NX options at the same time are not compatible"
);
return
;
}
if
(
incr
&&
elements
>
1
)
{
addReplyError
(
c
,
"INCR option supports a single increment-element pair"
);
return
;
}
/* Start parsing all the scores, we need to emit any syntax error
/* Start parsing all the scores, we need to emit any syntax error
* before executing additions to the sorted set, as the command should
* before executing additions to the sorted set, as the command should
* either execute fully or nothing at all. */
* either execute fully or nothing at all. */
scores
=
zmalloc
(
sizeof
(
double
)
*
elements
);
scores
=
zmalloc
(
sizeof
(
double
)
*
elements
);
for
(
j
=
0
;
j
<
elements
;
j
++
)
{
for
(
j
=
0
;
j
<
elements
;
j
++
)
{
if
(
getDoubleFromObjectOrReply
(
c
,
c
->
argv
[
2
+
j
*
2
],
&
scores
[
j
],
NULL
)
if
(
getDoubleFromObjectOrReply
(
c
,
c
->
argv
[
scoreidx
+
j
*
2
],
&
scores
[
j
],
NULL
)
!=
REDIS_OK
)
goto
cleanup
;
!=
REDIS_OK
)
goto
cleanup
;
}
}
/* Lookup the key and create the sorted set if does not exist. */
/* Lookup the key and create the sorted set if does not exist. */
zobj
=
lookupKeyWrite
(
c
->
db
,
key
);
zobj
=
lookupKeyWrite
(
c
->
db
,
key
);
if
(
zobj
==
NULL
)
{
if
(
zobj
==
NULL
)
{
if
(
xx
)
goto
reply_to_client
;
/* No key + XX option: nothing to do. */
if
(
server
.
zset_max_ziplist_entries
==
0
||
if
(
server
.
zset_max_ziplist_entries
==
0
||
server
.
zset_max_ziplist_value
<
sdslen
(
c
->
argv
[
3
]
->
ptr
))
server
.
zset_max_ziplist_value
<
sdslen
(
c
->
argv
[
3
]
->
ptr
))
{
{
...
@@ -1220,8 +1269,9 @@ void zaddGenericCommand(redisClient *c, int incr) {
...
@@ -1220,8 +1269,9 @@ void zaddGenericCommand(redisClient *c, int incr) {
unsigned
char
*
eptr
;
unsigned
char
*
eptr
;
/* Prefer non-encoded element when dealing with ziplists. */
/* Prefer non-encoded element when dealing with ziplists. */
ele
=
c
->
argv
[
3
+
j
*
2
];
ele
=
c
->
argv
[
scoreidx
+
1
+
j
*
2
];
if
((
eptr
=
zzlFind
(
zobj
->
ptr
,
ele
,
&
curscore
))
!=
NULL
)
{
if
((
eptr
=
zzlFind
(
zobj
->
ptr
,
ele
,
&
curscore
))
!=
NULL
)
{
if
(
nx
)
continue
;
if
(
incr
)
{
if
(
incr
)
{
score
+=
curscore
;
score
+=
curscore
;
if
(
isnan
(
score
))
{
if
(
isnan
(
score
))
{
...
@@ -1237,7 +1287,8 @@ void zaddGenericCommand(redisClient *c, int incr) {
...
@@ -1237,7 +1287,8 @@ void zaddGenericCommand(redisClient *c, int incr) {
server
.
dirty
++
;
server
.
dirty
++
;
updated
++
;
updated
++
;
}
}
}
else
{
processed
++
;
}
else
if
(
!
xx
)
{
/* Optimize: check if the element is too large or the list
/* Optimize: check if the element is too large or the list
* becomes too long *before* executing zzlInsert. */
* becomes too long *before* executing zzlInsert. */
zobj
->
ptr
=
zzlInsert
(
zobj
->
ptr
,
ele
,
score
);
zobj
->
ptr
=
zzlInsert
(
zobj
->
ptr
,
ele
,
score
);
...
@@ -1247,15 +1298,18 @@ void zaddGenericCommand(redisClient *c, int incr) {
...
@@ -1247,15 +1298,18 @@ void zaddGenericCommand(redisClient *c, int incr) {
zsetConvert
(
zobj
,
REDIS_ENCODING_SKIPLIST
);
zsetConvert
(
zobj
,
REDIS_ENCODING_SKIPLIST
);
server
.
dirty
++
;
server
.
dirty
++
;
added
++
;
added
++
;
processed
++
;
}
}
}
else
if
(
zobj
->
encoding
==
REDIS_ENCODING_SKIPLIST
)
{
}
else
if
(
zobj
->
encoding
==
REDIS_ENCODING_SKIPLIST
)
{
zset
*
zs
=
zobj
->
ptr
;
zset
*
zs
=
zobj
->
ptr
;
zskiplistNode
*
znode
;
zskiplistNode
*
znode
;
dictEntry
*
de
;
dictEntry
*
de
;
ele
=
c
->
argv
[
3
+
j
*
2
]
=
tryObjectEncoding
(
c
->
argv
[
3
+
j
*
2
]);
ele
=
c
->
argv
[
scoreidx
+
1
+
j
*
2
]
=
tryObjectEncoding
(
c
->
argv
[
scoreidx
+
1
+
j
*
2
]);
de
=
dictFind
(
zs
->
dict
,
ele
);
de
=
dictFind
(
zs
->
dict
,
ele
);
if
(
de
!=
NULL
)
{
if
(
de
!=
NULL
)
{
if
(
nx
)
continue
;
curobj
=
dictGetKey
(
de
);
curobj
=
dictGetKey
(
de
);
curscore
=
*
(
double
*
)
dictGetVal
(
de
);
curscore
=
*
(
double
*
)
dictGetVal
(
de
);
...
@@ -1280,22 +1334,30 @@ void zaddGenericCommand(redisClient *c, int incr) {
...
@@ -1280,22 +1334,30 @@ void zaddGenericCommand(redisClient *c, int incr) {
server
.
dirty
++
;
server
.
dirty
++
;
updated
++
;
updated
++
;
}
}
}
else
{
processed
++
;
}
else
if
(
!
xx
)
{
znode
=
zslInsert
(
zs
->
zsl
,
score
,
ele
);
znode
=
zslInsert
(
zs
->
zsl
,
score
,
ele
);
incrRefCount
(
ele
);
/* Inserted in skiplist. */
incrRefCount
(
ele
);
/* Inserted in skiplist. */
redisAssertWithInfo
(
c
,
NULL
,
dictAdd
(
zs
->
dict
,
ele
,
&
znode
->
score
)
==
DICT_OK
);
redisAssertWithInfo
(
c
,
NULL
,
dictAdd
(
zs
->
dict
,
ele
,
&
znode
->
score
)
==
DICT_OK
);
incrRefCount
(
ele
);
/* Added to dictionary. */
incrRefCount
(
ele
);
/* Added to dictionary. */
server
.
dirty
++
;
server
.
dirty
++
;
added
++
;
added
++
;
processed
++
;
}
}
}
else
{
}
else
{
redisPanic
(
"Unknown sorted set encoding"
);
redisPanic
(
"Unknown sorted set encoding"
);
}
}
}
}
if
(
incr
)
/* ZINCRBY */
reply_to_client:
if
(
incr
)
{
/* ZINCRBY or INCR option. */
if
(
processed
)
addReplyDouble
(
c
,
score
);
addReplyDouble
(
c
,
score
);
else
/* ZADD */
else
addReplyLongLong
(
c
,
added
);
addReply
(
c
,
shared
.
nullbulk
);
}
else
{
/* ZADD. */
addReplyLongLong
(
c
,
ch
?
added
+
updated
:
added
);
}
cleanup:
cleanup:
zfree
(
scores
);
zfree
(
scores
);
...
@@ -1307,11 +1369,11 @@ cleanup:
...
@@ -1307,11 +1369,11 @@ cleanup:
}
}
void
zaddCommand
(
redisClient
*
c
)
{
void
zaddCommand
(
redisClient
*
c
)
{
zaddGenericCommand
(
c
,
0
);
zaddGenericCommand
(
c
,
ZADD_NONE
);
}
}
void
zincrbyCommand
(
redisClient
*
c
)
{
void
zincrbyCommand
(
redisClient
*
c
)
{
zaddGenericCommand
(
c
,
1
);
zaddGenericCommand
(
c
,
ZADD_INCR
);
}
}
void
zremCommand
(
redisClient
*
c
)
{
void
zremCommand
(
redisClient
*
c
)
{
...
...
tests/unit/type/zset.tcl
View file @
28a250d9
...
@@ -43,6 +43,84 @@ start_server {tags {"zset"}} {
...
@@ -43,6 +43,84 @@ start_server {tags {"zset"}} {
assert_error
"*not*float*"
{
r zadd myzset nan abc
}
assert_error
"*not*float*"
{
r zadd myzset nan abc
}
}
}
test
"ZADD with options syntax error with incomplete pair"
{
r del ztmp
catch
{
r zadd ztmp xx 10 x 20
}
err
set err
}
{
ERR*
}
test
"ZADD XX option without key -
$encoding
"
{
r del ztmp
assert
{[
r zadd ztmp xx 10 x
]
== 0
}
assert
{[
r type ztmp
]
eq
{
none
}}
}
test
"ZADD XX existing key -
$encoding
"
{
r del ztmp
r zadd ztmp 10 x
assert
{[
r zadd ztmp xx 20 y
]
== 0
}
assert
{[
r zcard ztmp
]
== 1
}
}
test
"ZADD XX returns the number of elements actually added"
{
r del ztmp
r zadd ztmp 10 x
set retval
[
r zadd ztmp 10 x 20 y 30 z
]
assert
{
$retval
== 2
}
}
test
"ZADD XX updates existing elements score"
{
r del ztmp
r zadd ztmp 10 x 20 y 30 z
r zadd ztmp xx 5 foo 11 x 21 y 40 zap
assert
{[
r zcard ztmp
]
== 3
}
assert
{[
r zscore ztmp x
]
== 11
}
assert
{[
r zscore ztmp y
]
== 21
}
}
test
"ZADD XX and NX are not compatible"
{
r del ztmp
catch
{
r zadd ztmp xx nx 10 x
}
err
set err
}
{
ERR*
}
test
"ZADD NX with non exisitng key"
{
r del ztmp
r zadd ztmp nx 10 x 20 y 30 z
assert
{[
r zcard ztmp
]
== 3
}
}
test
"ZADD NX only add new elements without updating old ones"
{
r del ztmp
r zadd ztmp 10 x 20 y 30 z
assert
{[
r zadd ztmp nx 11 x 21 y 100 a 200 b
]
== 2
}
assert
{[
r zscore ztmp x
]
== 10
}
assert
{[
r zscore ztmp y
]
== 20
}
assert
{[
r zscore ztmp a
]
== 100
}
assert
{[
r zscore ztmp b
]
== 200
}
}
test
"ZADD INCR works like ZINCRBY"
{
r del ztmp
r zadd ztmp 10 x 20 y 30 z
r zadd ztmp INCR 15 x
assert
{[
r zscore ztmp x
]
== 25
}
}
test
"ZADD INCR works with a single score-elemenet pair"
{
r del ztmp
r zadd ztmp 10 x 20 y 30 z
catch
{
r zadd ztmp INCR 15 x 10 y
}
err
set err
}
{
ERR*
}
test
"ZADD CH option changes return value to all changed elements"
{
r del ztmp
r zadd ztmp 10 x 20 y 30 z
assert
{[
r zadd ztmp 11 x 21 y 30 z
]
== 0
}
assert
{[
r zadd ztmp ch 12 x 22 y 30 z
]
== 2
}
}
test
"ZINCRBY calls leading to NaN result in error"
{
test
"ZINCRBY calls leading to NaN result in error"
{
r zincrby myzset +inf abc
r zincrby myzset +inf abc
assert_error
"*NaN*"
{
r zincrby myzset -inf abc
}
assert_error
"*NaN*"
{
r zincrby myzset -inf abc
}
...
@@ -77,6 +155,8 @@ start_server {tags {"zset"}} {
...
@@ -77,6 +155,8 @@ start_server {tags {"zset"}} {
}
}
test
"ZCARD basics -
$encoding
"
{
test
"ZCARD basics -
$encoding
"
{
r del ztmp
r zadd ztmp 10 a 20 b 30 c
assert_equal 3
[
r zcard ztmp
]
assert_equal 3
[
r zcard ztmp
]
assert_equal 0
[
r zcard zdoesntexist
]
assert_equal 0
[
r zcard zdoesntexist
]
}
}
...
...
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