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
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