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
80458d04
Commit
80458d04
authored
Sep 26, 2024
by
YaacovHazan
Browse files
Merge remote-tracking branch 'upstream/unstable' into HEAD
parents
5fe3e74a
99c40ab5
Changes
34
Show whitespace changes
Inline
Side-by-side
src/redismodule.h
View file @
80458d04
...
...
@@ -60,10 +60,13 @@ typedef long long ustime_t;
#define REDISMODULE_OPEN_KEY_NOEXPIRE (1<<19)
/* Avoid any effects from fetching the key */
#define REDISMODULE_OPEN_KEY_NOEFFECTS (1<<20)
/* Allow access expired key that haven't deleted yet */
#define REDISMODULE_OPEN_KEY_ACCESS_EXPIRED (1<<21)
/* Mask of all REDISMODULE_OPEN_KEY_* values. Any new mode should be added to this list.
* Should not be used directly by the module, use RM_GetOpenKeyModesAll instead.
* Located here so when we will add new modes we will not forget to update it. */
#define _REDISMODULE_OPEN_KEY_ALL REDISMODULE_READ | REDISMODULE_WRITE | REDISMODULE_OPEN_KEY_NOTOUCH | REDISMODULE_OPEN_KEY_NONOTIFY | REDISMODULE_OPEN_KEY_NOSTATS | REDISMODULE_OPEN_KEY_NOEXPIRE | REDISMODULE_OPEN_KEY_NOEFFECTS
#define _REDISMODULE_OPEN_KEY_ALL REDISMODULE_READ | REDISMODULE_WRITE | REDISMODULE_OPEN_KEY_NOTOUCH | REDISMODULE_OPEN_KEY_NONOTIFY | REDISMODULE_OPEN_KEY_NOSTATS | REDISMODULE_OPEN_KEY_NOEXPIRE | REDISMODULE_OPEN_KEY_NOEFFECTS
| REDISMODULE_OPEN_KEY_ACCESS_EXPIRED
/* List push and pop */
#define REDISMODULE_LIST_HEAD 0
...
...
src/resp_parser.c
View file @
80458d04
...
...
@@ -34,6 +34,7 @@
* ----------------------------------------------------------------------------------------
*/
#include "fast_float_strtod.h"
#include "resp_parser.h"
#include "server.h"
...
...
@@ -132,7 +133,7 @@ static int parseDouble(ReplyParser *parser, void *p_ctx) {
if
(
len
<=
MAX_LONG_DOUBLE_CHARS
)
{
memcpy
(
buf
,
proto
+
1
,
len
);
buf
[
len
]
=
'\0'
;
d
=
strtod
(
buf
,
NULL
);
/* We expect a valid representation. */
d
=
fast_float_
strtod
(
buf
,
NULL
);
/* We expect a valid representation. */
}
else
{
d
=
0
;
}
...
...
src/server.h
View file @
80458d04
...
...
@@ -3221,6 +3221,7 @@ typedef struct dictExpireMetadata {
#define HFE_LAZY_AVOID_HASH_DEL (1<<1)
/* Avoid deleting hash if the field is the last one */
#define HFE_LAZY_NO_NOTIFICATION (1<<2)
/* Do not send notification, used when multiple fields
* may expire and only one notification is desired. */
#define HFE_LAZY_ACCESS_EXPIRED (1<<3)
/* Avoid lazy expire and allow access to expired fields */
void
hashTypeConvert
(
robj
*
o
,
int
enc
,
ebuckets
*
hexpires
);
void
hashTypeTryConversion
(
redisDb
*
db
,
robj
*
subject
,
robj
**
argv
,
int
start
,
int
end
);
...
...
@@ -3388,6 +3389,7 @@ int objectSetLRUOrLFU(robj *val, long long lfu_freq, long long lru_idle,
#define LOOKUP_NOSTATS (1<<2)
/* Don't update keyspace hits/misses counters. */
#define LOOKUP_WRITE (1<<3)
/* Delete expired keys even in replicas. */
#define LOOKUP_NOEXPIRE (1<<4)
/* Avoid deleting lazy expired keys. */
#define LOOKUP_ACCESS_EXPIRED (1<<5)
/* Allow lookup to expired key. */
#define LOOKUP_NOEFFECTS (LOOKUP_NONOTIFY | LOOKUP_NOSTATS | LOOKUP_NOTOUCH | LOOKUP_NOEXPIRE)
/* Avoid any effects from fetching the key */
dictEntry
*
dbAdd
(
redisDb
*
db
,
robj
*
key
,
robj
*
val
);
...
...
src/sort.c
View file @
80458d04
...
...
@@ -7,7 +7,7 @@
* (RSALv2) or the Server Side Public License v1 (SSPLv1).
*/
#include "fast_float_strtod.h"
#include "server.h"
#include "pqsort.h"
/* Partial qsort for SORT+LIMIT */
#include <math.h>
/* isnan() */
...
...
@@ -472,7 +472,7 @@ void sortCommandGeneric(client *c, int readonly) {
if
(
sdsEncodedObject
(
byval
))
{
char
*
eptr
;
vector
[
j
].
u
.
score
=
strtod
(
byval
->
ptr
,
&
eptr
);
vector
[
j
].
u
.
score
=
fast_float_
strtod
(
byval
->
ptr
,
&
eptr
);
if
(
eptr
[
0
]
!=
'\0'
||
errno
==
ERANGE
||
isnan
(
vector
[
j
].
u
.
score
))
{
...
...
src/t_hash.c
View file @
80458d04
...
...
@@ -734,7 +734,7 @@ GetFieldRes hashTypeGetValue(redisDb *db, robj *o, sds field, unsigned char **vs
serverPanic
(
"Unknown hash encoding"
);
}
if
(
expiredAt
>=
(
uint64_t
)
commandTimeSnapshot
())
if
(
(
expiredAt
>=
(
uint64_t
)
commandTimeSnapshot
())
||
(
hfeFlags
&
HFE_LAZY_ACCESS_EXPIRED
))
return
GETF_OK
;
if
(
server
.
masterhost
)
{
...
...
@@ -1134,6 +1134,20 @@ int hashTypeSetExInit(robj *key, robj *o, client *c, redisDb *db, const char *cm
dictEntry
*
de
=
dbFind
(
c
->
db
,
key
->
ptr
);
serverAssert
(
de
!=
NULL
);
lpt
->
key
=
dictGetKey
(
de
);
}
else
if
(
o
->
encoding
==
OBJ_ENCODING_LISTPACK_EX
)
{
listpackEx
*
lpt
=
o
->
ptr
;
/* If the hash previously had HFEs but later no longer does, the key ref
* (lpt->key) in the hash might become outdated after a MOVE/COPY/RENAME/RESTORE
* operation. These commands maintain the key ref only if HFEs are present.
* That is, we can only be sure that key ref is valid as long as it is not
* "trash". (TODO: dbFind() can be avoided. Instead need to extend the
* lookupKey*() to return dictEntry). */
if
(
lpt
->
meta
.
trash
)
{
dictEntry
*
de
=
dbFind
(
c
->
db
,
key
->
ptr
);
serverAssert
(
de
!=
NULL
);
lpt
->
key
=
dictGetKey
(
de
);
}
}
else
if
(
o
->
encoding
==
OBJ_ENCODING_HT
)
{
/* Take care dict has HFE metadata */
if
(
!
isDictWithMetaHFE
(
ht
))
{
...
...
@@ -1151,6 +1165,18 @@ int hashTypeSetExInit(robj *key, robj *o, client *c, redisDb *db, const char *cm
m
->
key
=
dictGetKey
(
de
);
/* reference key in keyspace */
m
->
hfe
=
ebCreate
();
/* Allocate HFE DS */
m
->
expireMeta
.
trash
=
1
;
/* mark as trash (as long it wasn't ebAdd()) */
}
else
{
dictExpireMetadata
*
m
=
(
dictExpireMetadata
*
)
dictMetadata
(
ht
);
/* If the hash previously had HFEs but later no longer does, the key ref
* (m->key) in the hash might become outdated after a MOVE/COPY/RENAME/RESTORE
* operation. These commands maintain the key ref only if HFEs are present.
* That is, we can only be sure that key ref is valid as long as it is not
* "trash". */
if
(
m
->
expireMeta
.
trash
)
{
dictEntry
*
de
=
dbFind
(
db
,
key
->
ptr
);
serverAssert
(
de
!=
NULL
);
m
->
key
=
dictGetKey
(
de
);
/* reference key in keyspace */
}
}
}
...
...
src/t_set.c
View file @
80458d04
...
...
@@ -2,8 +2,13 @@
* Copyright (c) 2009-Present, Redis Ltd.
* All rights reserved.
*
* Copyright (c) 2024-present, Valkey contributors.
* All rights reserved.
*
* Licensed under your choice of the Redis Source Available License 2.0
* (RSALv2) or the Server Side Public License v1 (SSPLv1).
*
* Portions of this file are available under BSD3 terms; see REDISCONTRIBUTIONS for more information.
*/
#include "server.h"
...
...
@@ -1492,6 +1497,7 @@ void sunionDiffGenericCommand(client *c, robj **setkeys, int setnum,
robj
**
sets
=
zmalloc
(
sizeof
(
robj
*
)
*
setnum
);
setTypeIterator
*
si
;
robj
*
dstset
=
NULL
;
int
dstset_encoding
=
OBJ_ENCODING_INTSET
;
char
*
str
;
size_t
len
;
int64_t
llval
;
...
...
@@ -1510,6 +1516,23 @@ void sunionDiffGenericCommand(client *c, robj **setkeys, int setnum,
zfree
(
sets
);
return
;
}
/* For a SET's encoding, according to the factory method setTypeCreate(), currently have 3 types:
* 1. OBJ_ENCODING_INTSET
* 2. OBJ_ENCODING_LISTPACK
* 3. OBJ_ENCODING_HT
* 'dstset_encoding' is used to determine which kind of encoding to use when initialize 'dstset'.
*
* If all sets are all OBJ_ENCODING_INTSET encoding or 'dstkey' is not null, keep 'dstset'
* OBJ_ENCODING_INTSET encoding when initialize. Otherwise it is not efficient to create the 'dstset'
* from intset and then convert to listpack or hashtable.
*
* If one of the set is OBJ_ENCODING_LISTPACK, let's set 'dstset' to hashtable default encoding,
* the hashtable is more efficient when find and compare than the listpack. The corresponding
* time complexity are O(1) vs O(n). */
if
(
!
dstkey
&&
dstset_encoding
==
OBJ_ENCODING_INTSET
&&
(
setobj
->
encoding
==
OBJ_ENCODING_LISTPACK
||
setobj
->
encoding
==
OBJ_ENCODING_HT
))
{
dstset_encoding
=
OBJ_ENCODING_HT
;
}
sets
[
j
]
=
setobj
;
if
(
j
>
0
&&
sets
[
0
]
==
sets
[
j
])
{
sameset
=
1
;
...
...
@@ -1552,7 +1575,11 @@ void sunionDiffGenericCommand(client *c, robj **setkeys, int setnum,
/* We need a temp set object to store our union/diff. If the dstkey
* is not NULL (that is, we are inside an SUNIONSTORE/SDIFFSTORE operation) then
* this set object will be the resulting object to set into the target key*/
if
(
dstset_encoding
==
OBJ_ENCODING_INTSET
)
{
dstset
=
createIntsetObject
();
}
else
{
dstset
=
createSetObject
();
}
if
(
op
==
SET_OP_UNION
)
{
/* Union is trivial, just add every element of every set to the
...
...
src/t_zset.c
View file @
80458d04
/*
*
Copyright (c) 2009-current, Redis Ltd.
* Copyright (c) 2009-
2012, Pieter Noordhuis <pcnoordhuis at gmail dot com>
/*
t_zset.c -- zset data type implementation.
*
* Copyright (c) 2009-
Present, Redis Ltd.
* All rights reserved.
*
*
Redistribution and use in source and binary forms, with or without
*
modification, are permitted provided that the following conditions are met:
*
Copyright (c) 2024-present, Valkey contributors.
*
All rights reserved.
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of Redis nor the names of its contributors may be used
* to endorse or promote products derived from this software without
* specific prior written permission.
* Licensed under your choice of the Redis Source Available License 2.0
* (RSALv2) or the Server Side Public License v1 (SSPLv1).
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
* Portions of this file are available under BSD3 terms; see REDISCONTRIBUTIONS for more information.
*/
/*-----------------------------------------------------------------------------
...
...
@@ -55,7 +39,7 @@
* c) there is a back pointer, so it's a doubly linked list with the back
* pointers being only at "level 1". This allows to traverse the list
* from tail to head, useful for ZREVRANGE. */
#include "fast_float_strtod.h"
#include "server.h"
#include "intset.h"
/* Compact integer set structure */
#include <math.h>
...
...
@@ -566,11 +550,11 @@ static int zslParseRange(robj *min, robj *max, zrangespec *spec) {
spec
->
min
=
(
long
)
min
->
ptr
;
}
else
{
if
(((
char
*
)
min
->
ptr
)[
0
]
==
'('
)
{
spec
->
min
=
strtod
((
char
*
)
min
->
ptr
+
1
,
&
eptr
);
spec
->
min
=
fast_float_
strtod
((
char
*
)
min
->
ptr
+
1
,
&
eptr
);
if
(
eptr
[
0
]
!=
'\0'
||
isnan
(
spec
->
min
))
return
C_ERR
;
spec
->
minex
=
1
;
}
else
{
spec
->
min
=
strtod
((
char
*
)
min
->
ptr
,
&
eptr
);
spec
->
min
=
fast_float_
strtod
((
char
*
)
min
->
ptr
,
&
eptr
);
if
(
eptr
[
0
]
!=
'\0'
||
isnan
(
spec
->
min
))
return
C_ERR
;
}
}
...
...
@@ -578,11 +562,11 @@ static int zslParseRange(robj *min, robj *max, zrangespec *spec) {
spec
->
max
=
(
long
)
max
->
ptr
;
}
else
{
if
(((
char
*
)
max
->
ptr
)[
0
]
==
'('
)
{
spec
->
max
=
strtod
((
char
*
)
max
->
ptr
+
1
,
&
eptr
);
spec
->
max
=
fast_float_
strtod
((
char
*
)
max
->
ptr
+
1
,
&
eptr
);
if
(
eptr
[
0
]
!=
'\0'
||
isnan
(
spec
->
max
))
return
C_ERR
;
spec
->
maxex
=
1
;
}
else
{
spec
->
max
=
strtod
((
char
*
)
max
->
ptr
,
&
eptr
);
spec
->
max
=
fast_float_
strtod
((
char
*
)
max
->
ptr
,
&
eptr
);
if
(
eptr
[
0
]
!=
'\0'
||
isnan
(
spec
->
max
))
return
C_ERR
;
}
}
...
...
@@ -789,7 +773,7 @@ double zzlStrtod(unsigned char *vstr, unsigned int vlen) {
vlen
=
sizeof
(
buf
)
-
1
;
memcpy
(
buf
,
vstr
,
vlen
);
buf
[
vlen
]
=
'\0'
;
return
strtod
(
buf
,
NULL
);
return
fast_float_
strtod
(
buf
,
NULL
);
}
double
zzlGetScore
(
unsigned
char
*
sptr
)
{
...
...
@@ -2611,16 +2595,6 @@ static void zdiff(zsetopsrc *src, long setnum, zset *dstzset, size_t *maxelelen,
}
}
dictType
setAccumulatorDictType
=
{
dictSdsHash
,
/* hash function */
NULL
,
/* key dup */
NULL
,
/* val dup */
dictSdsKeyCompare
,
/* key compare */
NULL
,
/* key destructor */
NULL
,
/* val destructor */
NULL
/* allow to expand */
};
/* The zunionInterDiffGenericCommand() function is called in order to implement the
* following commands: ZUNION, ZINTER, ZDIFF, ZUNIONSTORE, ZINTERSTORE, ZDIFFSTORE,
* ZINTERCARD.
...
...
@@ -2816,7 +2790,6 @@ void zunionInterDiffGenericCommand(client *c, robj *dstkey, int numkeysIndex, in
zuiClearIterator
(
&
src
[
0
]);
}
}
else
if
(
op
==
SET_OP_UNION
)
{
dict
*
accumulator
=
dictCreate
(
&
setAccumulatorDictType
);
dictIterator
*
di
;
dictEntry
*
de
,
*
existing
;
double
score
;
...
...
@@ -2824,7 +2797,7 @@ void zunionInterDiffGenericCommand(client *c, robj *dstkey, int numkeysIndex, in
if
(
setnum
)
{
/* Our union is at least as large as the largest set.
* Resize the dictionary ASAP to avoid useless rehashing. */
dictExpand
(
accumulator
,
zuiLength
(
&
src
[
setnum
-
1
]));
dictExpand
(
dstzset
->
dict
,
zuiLength
(
&
src
[
setnum
-
1
]));
}
/* Step 1: Create a dictionary of elements -> aggregated-scores
...
...
@@ -2839,7 +2812,7 @@ void zunionInterDiffGenericCommand(client *c, robj *dstkey, int numkeysIndex, in
if
(
isnan
(
score
))
score
=
0
;
/* Search for this element in the accumulating dictionary. */
de
=
dictAddRaw
(
accumulator
,
zuiSdsFromValue
(
&
zval
),
&
existing
);
de
=
dictAddRaw
(
dstzset
->
dict
,
zuiSdsFromValue
(
&
zval
),
&
existing
);
/* If we don't have it, we need to create a new entry. */
if
(
!
existing
)
{
tmp
=
zuiNewSdsFromValue
(
&
zval
);
...
...
@@ -2849,7 +2822,7 @@ void zunionInterDiffGenericCommand(client *c, robj *dstkey, int numkeysIndex, in
totelelen
+=
sdslen
(
tmp
);
if
(
sdslen
(
tmp
)
>
maxelelen
)
maxelelen
=
sdslen
(
tmp
);
/* Update the element with its initial score. */
dictSetKey
(
accumulator
,
de
,
tmp
);
dictSetKey
(
dstzset
->
dict
,
de
,
tmp
);
dictSetDoubleVal
(
de
,
score
);
}
else
{
/* Update the score with the score of the new instance
...
...
@@ -2866,21 +2839,15 @@ void zunionInterDiffGenericCommand(client *c, robj *dstkey, int numkeysIndex, in
}
/* Step 2: convert the dictionary into the final sorted set. */
di
=
dictGetIterator
(
accumulator
);
/* We now are aware of the final size of the resulting sorted set,
* let's resize the dictionary embedded inside the sorted set to the
* right size, in order to save rehashing time. */
dictExpand
(
dstzset
->
dict
,
dictSize
(
accumulator
));
di
=
dictGetIterator
(
dstzset
->
dict
);
while
((
de
=
dictNext
(
di
))
!=
NULL
)
{
sds
ele
=
dictGetKey
(
de
);
score
=
dictGetDoubleVal
(
de
);
znode
=
zslInsert
(
dstzset
->
zsl
,
score
,
ele
);
dict
Add
(
dstzset
->
dict
,
el
e
,
&
znode
->
score
);
dict
SetVal
(
dstzset
->
dict
,
d
e
,
&
znode
->
score
);
}
dictReleaseIterator
(
di
);
dictRelease
(
accumulator
);
}
else
if
(
op
==
SET_OP_DIFF
)
{
zdiff
(
src
,
setnum
,
dstzset
,
&
maxelelen
,
&
totelelen
);
}
else
{
...
...
src/util.c
View file @
80458d04
...
...
@@ -30,6 +30,7 @@
#include "fmacros.h"
#include "fpconv_dtoa.h"
#include "fast_float_strtod.h"
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
...
...
@@ -612,7 +613,7 @@ int string2ld(const char *s, size_t slen, long double *dp) {
int
string2d
(
const
char
*
s
,
size_t
slen
,
double
*
dp
)
{
errno
=
0
;
char
*
eptr
;
*
dp
=
strtod
(
s
,
&
eptr
);
*
dp
=
fast_float_
strtod
(
s
,
&
eptr
);
if
(
slen
==
0
||
isspace
(((
const
char
*
)
s
)[
0
])
||
(
size_t
)(
eptr
-
(
char
*
)
s
)
!=
slen
||
...
...
tests/integration/corrupt-dump.tcl
View file @
80458d04
...
...
@@ -897,5 +897,39 @@ test {corrupt payload: fuzzer findings - set with invalid length causes smembers
}
}
test
{
corrupt payload: fuzzer findings - set with invalid length causes sscan to hang
}
{
start_server
[
list overrides
[
list loglevel verbose use-exit-on-panic yes crash-memcheck-enabled no
]
]
{
# In the past, it generated a broken protocol and left the client hung in smembers
r config set sanitize-dump-payload no
assert_equal
{
OK
}
[
r restore _set 0
"
\x14\x16\x16\x00\x00\x00\x0c\x00\x81\x61\x02\x81\x62\x02\x81\x63\x02\x01\x01\x02\x01\x03\x01\xff\x0c\x00\x91\x00\x56\x73\xc1\x82\xd5\xbd
"
replace
]
assert_encoding listpack _set
catch
{
r SSCAN _set 0
}
err
assert_equal
[
count_log_message 0
"crashed by signal"
]
0
assert_equal
[
count_log_message 0
"ASSERTION FAILED"
]
1
}
}
test
{
corrupt payload: zset listpack encoded with invalid length causes zscan to hang
}
{
start_server
[
list overrides
[
list loglevel verbose use-exit-on-panic yes crash-memcheck-enabled no
]
]
{
r config set sanitize-dump-payload no
assert_equal
{
OK
}
[
r restore _zset 0
"
\x11\x16\x16\x00\x00\x00\x1a\x00\x81\x61\x02\x01\x01\x81\x62\x02\x02\x01\x81\x63\x02\x03\x01\xff\x0c\x00\x81\xa7\xcd\x31\x22\x6c\xef\xf7
"
replace
]
assert_encoding listpack _zset
catch
{
r ZSCAN _zset 0
}
err
assert_equal
[
count_log_message 0
"crashed by signal"
]
0
assert_equal
[
count_log_message 0
"ASSERTION FAILED"
]
1
}
}
test
{
corrupt payload: hash listpack encoded with invalid length causes hscan to hang
}
{
start_server
[
list overrides
[
list loglevel verbose use-exit-on-panic yes crash-memcheck-enabled no
]
]
{
r config set sanitize-dump-payload no
assert_equal
{
OK
}
[
r restore _hash 0
"
\x10\x17\x17\x00\x00\x00\x0e\x00\x82\x66\x31\x03\x82\x76\x31\x03\x82\x66\x32\x03\x82\x76\x32\x03\xff\x0c\x00\xf1\xc5\x36\x92\x29\x6a\x8c\xc5
"
replace
]
assert_encoding listpack _hash
catch
{
r HSCAN _hash 0
}
err
assert_equal
[
count_log_message 0
"crashed by signal"
]
0
assert_equal
[
count_log_message 0
"ASSERTION FAILED"
]
1
}
}
}
;
# tags
tests/integration/redis-cli.tcl
View file @
80458d04
...
...
@@ -808,6 +808,12 @@ if {!$::tls} { ;# fake_redis_node doesn't support TLS
assert_equal 3
[
exec
{*}
$cmdline
ZCARD new_zset
]
assert_equal
"a
\n
1
\n
b
\n
2
\n
c
\n
3"
[
exec
{*}
$cmdline
ZRANGE new_zset 0 -1 WITHSCORES
]
}
test
"Send eval command by using --eval option"
{
set tmpfile
[
write_tmpfile
{
return ARGV
[
1
]}]
set cmdline
[
rediscli
[
srv host
]
[
srv port
]]
assert_equal
"foo"
[
exec
{*}
$cmdline
--eval $tmpfile , foo
]
}
}
start_server
{
tags
{
"cli external:skip"
}}
{
...
...
tests/modules/hash.c
View file @
80458d04
...
...
@@ -3,6 +3,8 @@
#include <errno.h>
#include <stdlib.h>
#define UNUSED(x) (void)(x)
/* If a string is ":deleted:", the special value for deleted hash fields is
* returned; otherwise the input string is returned. */
static
RedisModuleString
*
value_or_delete
(
RedisModuleString
*
s
)
{
...
...
@@ -76,15 +78,100 @@ int hash_set(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
return
RedisModule_ReplyWithLongLong
(
ctx
,
result
);
}
RedisModuleKey
*
openKeyWithMode
(
RedisModuleCtx
*
ctx
,
RedisModuleString
*
keyName
,
int
mode
)
{
int
supportedMode
=
RedisModule_GetOpenKeyModesAll
();
if
(
!
(
supportedMode
&
REDISMODULE_READ
)
||
((
supportedMode
&
mode
)
!=
mode
))
{
RedisModule_ReplyWithError
(
ctx
,
"OpenKey mode is not supported"
);
return
NULL
;
}
RedisModuleKey
*
key
=
RedisModule_OpenKey
(
ctx
,
keyName
,
REDISMODULE_READ
|
mode
);
if
(
!
key
)
{
RedisModule_ReplyWithError
(
ctx
,
"key not found"
);
return
NULL
;
}
return
key
;
}
int
test_open_key_subexpired_hget
(
RedisModuleCtx
*
ctx
,
RedisModuleString
**
argv
,
int
argc
)
{
if
(
argc
<
3
)
{
RedisModule_WrongArity
(
ctx
);
return
REDISMODULE_OK
;
}
RedisModuleKey
*
key
=
openKeyWithMode
(
ctx
,
argv
[
1
],
REDISMODULE_OPEN_KEY_ACCESS_EXPIRED
);
if
(
!
key
)
return
REDISMODULE_OK
;
RedisModuleString
*
value
;
RedisModule_HashGet
(
key
,
REDISMODULE_HASH_NONE
,
argv
[
2
],
&
value
,
NULL
);
/* return the value */
if
(
value
)
{
RedisModule_ReplyWithString
(
ctx
,
value
);
RedisModule_FreeString
(
ctx
,
value
);
}
else
{
RedisModule_ReplyWithNull
(
ctx
);
}
RedisModule_CloseKey
(
key
);
return
REDISMODULE_OK
;
}
int
numReplies
;
void
ScanCallback
(
RedisModuleKey
*
key
,
RedisModuleString
*
field
,
RedisModuleString
*
value
,
void
*
privdata
)
{
UNUSED
(
key
);
RedisModuleCtx
*
ctx
=
(
RedisModuleCtx
*
)
privdata
;
/* Reply with the field and value (or NULL for sets) */
RedisModule_ReplyWithString
(
ctx
,
field
);
if
(
value
)
{
RedisModule_ReplyWithString
(
ctx
,
value
);
}
else
{
RedisModule_ReplyWithCString
(
ctx
,
"(null)"
);
}
numReplies
+=
2
;
}
int
test_open_key_access_expired_hscan
(
RedisModuleCtx
*
ctx
,
RedisModuleString
**
argv
,
int
argc
)
{
if
(
argc
<
2
)
{
RedisModule_WrongArity
(
ctx
);
return
REDISMODULE_OK
;
}
RedisModuleKey
*
key
=
openKeyWithMode
(
ctx
,
argv
[
1
],
REDISMODULE_OPEN_KEY_ACCESS_EXPIRED
);
if
(
!
key
)
return
RedisModule_ReplyWithError
(
ctx
,
"ERR key not exists"
);
/* Verify it is a hash */
if
(
RedisModule_KeyType
(
key
)
!=
REDISMODULE_KEYTYPE_HASH
)
{
RedisModule_CloseKey
(
key
);
return
RedisModule_ReplyWithError
(
ctx
,
"ERR key is not a hash"
);
}
/* Scan the hash and reply pairs of key-value */
RedisModule_ReplyWithArray
(
ctx
,
REDISMODULE_POSTPONED_ARRAY_LEN
);
numReplies
=
0
;
RedisModuleScanCursor
*
cursor
=
RedisModule_ScanCursorCreate
();
while
(
RedisModule_ScanKey
(
key
,
cursor
,
ScanCallback
,
ctx
));
RedisModule_ScanCursorDestroy
(
cursor
);
RedisModule_CloseKey
(
key
);
RedisModule_ReplySetArrayLength
(
ctx
,
numReplies
);
return
REDISMODULE_OK
;
}
int
RedisModule_OnLoad
(
RedisModuleCtx
*
ctx
,
RedisModuleString
**
argv
,
int
argc
)
{
REDISMODULE_NOT_USED
(
argv
);
REDISMODULE_NOT_USED
(
argc
);
if
(
RedisModule_Init
(
ctx
,
"hash"
,
1
,
REDISMODULE_APIVER_1
)
==
REDISMODULE_OK
&&
RedisModule_CreateCommand
(
ctx
,
"hash.set"
,
hash_set
,
"write"
,
1
,
1
,
1
)
==
REDISMODULE_OK
)
{
return
REDISMODULE_OK
;
}
else
{
if
(
RedisModule_Init
(
ctx
,
"hash"
,
1
,
REDISMODULE_APIVER_1
)
==
REDISMODULE_ERR
)
return
REDISMODULE_ERR
;
}
if
(
RedisModule_CreateCommand
(
ctx
,
"hash.set"
,
hash_set
,
"write"
,
1
,
1
,
1
)
==
REDISMODULE_ERR
)
return
REDISMODULE_ERR
;
if
(
RedisModule_CreateCommand
(
ctx
,
"hash.hget_expired"
,
test_open_key_subexpired_hget
,
""
,
0
,
0
,
0
)
==
REDISMODULE_ERR
)
return
REDISMODULE_ERR
;
if
(
RedisModule_CreateCommand
(
ctx
,
"hash.hscan_expired"
,
test_open_key_access_expired_hscan
,
""
,
0
,
0
,
0
)
==
REDISMODULE_ERR
)
return
REDISMODULE_ERR
;
return
REDISMODULE_OK
;
}
tests/unit/introspection-2.tcl
View file @
80458d04
...
...
@@ -51,6 +51,26 @@ start_server {tags {"introspection"}} {
assert_morethan $newlru $oldlru
}
{}
{
needs:debug
}
test
{
Operations in no-touch mode TOUCH alters the last access time of a key
}
{
r set foo bar
r client no-touch on
set oldlru
[
getlru foo
]
after 1100
r touch foo
set newlru
[
getlru foo
]
assert_morethan $newlru $oldlru
}
{}
{
needs:debug
}
test
{
Operations in no-touch mode TOUCH from script alters the last access time of a key
}
{
r set foo bar
r client no-touch on
set oldlru
[
getlru foo
]
after 1100
assert_equal
{
1
}
[
r eval
"return redis.call('touch', 'foo')"
0
]
set newlru
[
getlru foo
]
assert_morethan $newlru $oldlru
}
{}
{
needs:debug
}
test
{
TOUCH returns the number of existing keys specified
}
{
r flushdb
r set key1
{
t
}
1
...
...
tests/unit/moduleapi/hash.tcl
View file @
80458d04
...
...
@@ -60,6 +60,39 @@ start_server {tags {"modules"}} {
r debug set-active-expire 1
}
{
OK
}
{
needs:debug
}
test
{
test open key with REDISMODULE_OPEN_KEY_ACCESS_EXPIRED to scan expired fields
}
{
r debug set-active-expire 0
r del H1
r hash.set H1
"n"
f1 v1 f2 v2 f3 v3
r hpexpire H1 1 FIELDS 2 f1 f2
after 10
# Scan expired fields with flag REDISMODULE_OPEN_KEY_ACCESS_EXPIRED
assert_equal
"f1 f2 f3 v1 v2 v3"
[
lsort
[
r hash.hscan_expired H1
]]
# Get expired field with flag REDISMODULE_OPEN_KEY_ACCESS_EXPIRED
assert_equal
{
v1
}
[
r hash.hget_expired H1 f1
]
# Verify key doesn't exist on normal access without the flag
assert_equal 0
[
r hexists H1 f1
]
assert_equal 0
[
r hexists H1 f2
]
# Scan again expired fields with flag REDISMODULE_OPEN_KEY_ACCESS_EXPIRED
assert_equal
"f3 v3"
[
lsort
[
r hash.hscan_expired H1
]]
r debug set-active-expire 1
}
test
{
test open key with REDISMODULE_OPEN_KEY_ACCESS_EXPIRED to scan expired key
}
{
r debug set-active-expire 0
r del H1
r hash.set H1
"n"
f1 v1 f2 v2 f3 v3
r pexpire H1 5
after 10
# Scan expired fields with flag REDISMODULE_OPEN_KEY_ACCESS_EXPIRED
assert_equal
"f1 f2 f3 v1 v2 v3"
[
lsort
[
r hash.hscan_expired H1
]]
# Get expired field with flag REDISMODULE_OPEN_KEY_ACCESS_EXPIRED
assert_equal
{
v1
}
[
r hash.hget_expired H1 f1
]
# Verify key doesn't exist on normal access without the flag
assert_equal 0
[
r exists H1
]
r debug set-active-expire 1
}
test
"Unload the module - hash"
{
assert_equal
{
OK
}
[
r module unload hash
]
}
...
...
tests/unit/type/hash-field-expire.tcl
View file @
80458d04
...
...
@@ -199,12 +199,12 @@ start_server {tags {"external:skip needs:debug"}} {
set hash_sizes
{
1 15 16 17 31 32 33 40
}
foreach h $hash_sizes
{
for
{
set i 1
}
{
$i
<= $h
}
{
incr i
}
{
#
r
andom expiration time
#
R
andom expiration time
(
Take care expired not after
"mix
$h
"
)
r hset hrand$h f$i v$i
r hpexpire hrand$h
[
expr
{
5
0 + int
(
rand
()
*
5
0
)}]
FIELDS 1 f$i
r hpexpire hrand$h
[
expr
{
7
0 + int
(
rand
()
*
3
0
)}]
FIELDS 1 f$i
assert_equal 1
[
r HEXISTS hrand$h f$i
]
#
s
ame expiration time
#
S
ame expiration time
(
Take care expired not after
"mix
$h
"
)
r hset same$h f$i v$i
r hpexpire same$h 100 FIELDS 1 f$i
assert_equal 1
[
r HEXISTS same$h f$i
]
...
...
@@ -286,10 +286,9 @@ start_server {tags {"external:skip needs:debug"}} {
test
"HEXPIRETIME - returns TTL in Unix timestamp (
$type
)"
{
r del myhash
r HSET myhash field1 value1
r HPEXPIRE myhash 1000 NX FIELDS 1 field1
set lo
[
expr
{[
clock seconds
]
+ 1
}]
set hi
[
expr
{[
clock seconds
]
+ 2
}]
r HPEXPIRE myhash 1000 NX FIELDS 1 field1
assert_range
[
r HEXPIRETIME myhash FIELDS 1 field1
]
$lo $hi
assert_range
[
r HPEXPIRETIME myhash FIELDS 1 field1
]
[
expr $lo*1000
]
[
expr $hi*1000
]
}
...
...
@@ -599,6 +598,20 @@ start_server {tags {"external:skip needs:debug"}} {
wait_for_condition 30 10
{
[
r exists myhash2
]
== 0
}
else
{
fail
"`myhash2` should be expired"
}
}
test
"Test RENAME hash that had HFEs but not during the rename (
$type
)"
{
r del h1
r hset h1 f1 v1 f2 v2
r hpexpire h1 1 FIELDS 1 f1
after 20
r rename h1 h1_renamed
assert_equal
[
r exists h1
]
0
assert_equal
[
r exists h1_renamed
]
1
assert_equal
[
r hgetall h1_renamed
]
{
f2 v2
}
r hpexpire h1_renamed 1 FIELDS 1 f2
# Only active expire will delete the key
wait_for_condition 30 10
{
[
r exists h1_renamed
]
== 0
}
else
{
fail
"`h1_renamed` should be expired"
}
}
test
"MOVE to another DB hash with fields to be expired (
$type
)"
{
r select 9
r flushall
...
...
@@ -642,6 +655,20 @@ start_server {tags {"external:skip needs:debug"}} {
}
{}
{
singledb:skip
}
test
"Test COPY hash that had HFEs but not during the copy (
$type
)"
{
r del h1
r hset h1 f1 v1 f2 v2
r hpexpire h1 1 FIELDS 1 f1
after 20
r COPY h1 h1_copy
assert_equal
[
r exists h1
]
1
assert_equal
[
r exists h1_copy
]
1
assert_equal
[
r hgetall h1_copy
]
{
f2 v2
}
r hpexpire h1_copy 1 FIELDS 1 f2
# Only active expire will delete the key
wait_for_condition 30 10
{
[
r exists h1_copy
]
== 0
}
else
{
fail
"`h1_copy` should be expired"
}
}
test
"Test SWAPDB hash-fields to be expired (
$type
)"
{
r select 9
r flushall
...
...
@@ -663,6 +690,29 @@ start_server {tags {"external:skip needs:debug"}} {
wait_for_condition 20 10
{
[
r exists myhash
]
== 0
}
else
{
fail
"'myhash' should be expired"
}
}
{}
{
singledb:skip
}
test
"Test SWAPDB hash that had HFEs but not during the swap (
$type
)"
{
r select 9
r flushall
r hset myhash f1 v1 f2 v2
r hpexpire myhash 1 NX FIELDS 1 f1
after 10
r swapdb 9 10
# Verify the key and its field doesn't exist in the source DB
assert_equal
[
r exists myhash
]
0
assert_equal
[
r dbsize
]
0
# Verify the key and its field exists in the target DB
r select 10
assert_equal
[
r hgetall myhash
]
{
f2 v2
}
assert_equal
[
r dbsize
]
1
r hpexpire myhash 1 NX FIELDS 1 f2
# Eventually the field will be expired and the key will be deleted
wait_for_condition 20 10
{
[
r exists myhash
]
== 0
}
else
{
fail
"'myhash' should be expired"
}
}
{}
{
singledb:skip
}
test
"HMGET - returns empty entries if fields or hash expired (
$type
)"
{
r debug set-active-expire 0
r del h1 h2
...
...
@@ -731,6 +781,20 @@ start_server {tags {"external:skip needs:debug"}} {
assert_equal
[
r hexpiretime myhash FIELDS 3 a b c
]
{
2524600800 2524600801 -1
}
}
test
{
RESTORE hash that had in the past HFEs but not during the dump
}
{
r config set sanitize-dump-payload yes
r del myhash
r hmset myhash a 1 b 2 c 3
r hpexpire myhash 1 fields 1 a
after 10
set encoded
[
r dump myhash
]
r del myhash
r restore myhash 0 $encoded
assert_equal
[
lsort
[
r hgetall myhash
]]
"2 3 b c"
r hpexpire myhash 1 fields 2 b c
wait_for_condition 30 10
{
[
r exists myhash
]
== 0
}
else
{
fail
"`myhash` should be expired"
}
}
test
{
DUMP / RESTORE are able to serialize / unserialize a hash with TTL 0 for all fields
}
{
r config set sanitize-dump-payload yes
r del myhash
...
...
@@ -950,6 +1014,7 @@ start_server {tags {"external:skip needs:debug"}} {
start_server
{
overrides
{
appendonly
{
yes
}
appendfsync always
}
tags
{
external:skip
}}
{
set aof
[
get_last_incr_aof_path r
]
r debug set-active-expire 0
;
# Prevent fields from being expired during data preparation
# Time is in the past so it should propagate HDELs to replica
# and delete the fields
...
...
@@ -976,6 +1041,7 @@ start_server {tags {"external:skip needs:debug"}} {
r hpexpireat h2
[
expr
[
clock seconds
]
*1000+100000
]
LT FIELDS 1 f3
r hexpireat h2
[
expr
[
clock seconds
]
+10
]
NX FIELDS 1 f4
r debug set-active-expire 1
wait_for_condition 50 100
{
[
r hlen h2
]
eq 2
}
else
{
...
...
@@ -1004,7 +1070,7 @@ start_server {tags {"external:skip needs:debug"}} {
{
hdel h2 f2
}
}
}
}
}
{}
{
needs:debug
}
test
"Lazy Expire - fields are lazy deleted and propagated to replicas (
$type
)"
{
start_server
{
overrides
{
appendonly
{
yes
}
appendfsync always
}
tags
{
external:skip
}}
{
...
...
Prev
1
2
Next
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment