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
30eb6c32
Unverified
Commit
30eb6c32
authored
Sep 26, 2024
by
YaacovHazan
Committed by
GitHub
Sep 26, 2024
Browse files
Merge unstable into 8.0 (#13573)
parents
5fe3e74a
80458d04
Changes
34
Hide whitespace changes
Inline
Side-by-side
src/redismodule.h
View file @
30eb6c32
...
@@ -60,10 +60,13 @@ typedef long long ustime_t;
...
@@ -60,10 +60,13 @@ typedef long long ustime_t;
#define REDISMODULE_OPEN_KEY_NOEXPIRE (1<<19)
#define REDISMODULE_OPEN_KEY_NOEXPIRE (1<<19)
/* Avoid any effects from fetching the key */
/* Avoid any effects from fetching the key */
#define REDISMODULE_OPEN_KEY_NOEFFECTS (1<<20)
#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.
/* 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.
* 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. */
* 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 */
/* List push and pop */
#define REDISMODULE_LIST_HEAD 0
#define REDISMODULE_LIST_HEAD 0
...
...
src/resp_parser.c
View file @
30eb6c32
...
@@ -34,6 +34,7 @@
...
@@ -34,6 +34,7 @@
* ----------------------------------------------------------------------------------------
* ----------------------------------------------------------------------------------------
*/
*/
#include "fast_float_strtod.h"
#include "resp_parser.h"
#include "resp_parser.h"
#include "server.h"
#include "server.h"
...
@@ -132,7 +133,7 @@ static int parseDouble(ReplyParser *parser, void *p_ctx) {
...
@@ -132,7 +133,7 @@ static int parseDouble(ReplyParser *parser, void *p_ctx) {
if
(
len
<=
MAX_LONG_DOUBLE_CHARS
)
{
if
(
len
<=
MAX_LONG_DOUBLE_CHARS
)
{
memcpy
(
buf
,
proto
+
1
,
len
);
memcpy
(
buf
,
proto
+
1
,
len
);
buf
[
len
]
=
'\0'
;
buf
[
len
]
=
'\0'
;
d
=
strtod
(
buf
,
NULL
);
/* We expect a valid representation. */
d
=
fast_float_
strtod
(
buf
,
NULL
);
/* We expect a valid representation. */
}
else
{
}
else
{
d
=
0
;
d
=
0
;
}
}
...
...
src/server.h
View file @
30eb6c32
...
@@ -3221,6 +3221,7 @@ typedef struct dictExpireMetadata {
...
@@ -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_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
#define HFE_LAZY_NO_NOTIFICATION (1<<2)
/* Do not send notification, used when multiple fields
* may expire and only one notification is desired. */
* 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
hashTypeConvert
(
robj
*
o
,
int
enc
,
ebuckets
*
hexpires
);
void
hashTypeTryConversion
(
redisDb
*
db
,
robj
*
subject
,
robj
**
argv
,
int
start
,
int
end
);
void
hashTypeTryConversion
(
redisDb
*
db
,
robj
*
subject
,
robj
**
argv
,
int
start
,
int
end
);
...
@@ -3383,11 +3384,12 @@ robj *objectCommandLookupOrReply(client *c, robj *key, robj *reply);
...
@@ -3383,11 +3384,12 @@ robj *objectCommandLookupOrReply(client *c, robj *key, robj *reply);
int
objectSetLRUOrLFU
(
robj
*
val
,
long
long
lfu_freq
,
long
long
lru_idle
,
int
objectSetLRUOrLFU
(
robj
*
val
,
long
long
lfu_freq
,
long
long
lru_idle
,
long
long
lru_clock
,
int
lru_multiplier
);
long
long
lru_clock
,
int
lru_multiplier
);
#define LOOKUP_NONE 0
#define LOOKUP_NONE 0
#define LOOKUP_NOTOUCH (1<<0)
/* Don't update LRU. */
#define LOOKUP_NOTOUCH (1<<0)
/* Don't update LRU. */
#define LOOKUP_NONOTIFY (1<<1)
/* Don't trigger keyspace event on key misses. */
#define LOOKUP_NONOTIFY (1<<1)
/* Don't trigger keyspace event on key misses. */
#define LOOKUP_NOSTATS (1<<2)
/* Don't update keyspace hits/misses counters. */
#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_WRITE (1<<3)
/* Delete expired keys even in replicas. */
#define LOOKUP_NOEXPIRE (1<<4)
/* Avoid deleting lazy expired keys. */
#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 */
#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
);
dictEntry
*
dbAdd
(
redisDb
*
db
,
robj
*
key
,
robj
*
val
);
...
...
src/sort.c
View file @
30eb6c32
...
@@ -7,7 +7,7 @@
...
@@ -7,7 +7,7 @@
* (RSALv2) or the Server Side Public License v1 (SSPLv1).
* (RSALv2) or the Server Side Public License v1 (SSPLv1).
*/
*/
#include "fast_float_strtod.h"
#include "server.h"
#include "server.h"
#include "pqsort.h"
/* Partial qsort for SORT+LIMIT */
#include "pqsort.h"
/* Partial qsort for SORT+LIMIT */
#include <math.h>
/* isnan() */
#include <math.h>
/* isnan() */
...
@@ -472,7 +472,7 @@ void sortCommandGeneric(client *c, int readonly) {
...
@@ -472,7 +472,7 @@ void sortCommandGeneric(client *c, int readonly) {
if
(
sdsEncodedObject
(
byval
))
{
if
(
sdsEncodedObject
(
byval
))
{
char
*
eptr
;
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
||
if
(
eptr
[
0
]
!=
'\0'
||
errno
==
ERANGE
||
isnan
(
vector
[
j
].
u
.
score
))
isnan
(
vector
[
j
].
u
.
score
))
{
{
...
...
src/t_hash.c
View file @
30eb6c32
...
@@ -734,7 +734,7 @@ GetFieldRes hashTypeGetValue(redisDb *db, robj *o, sds field, unsigned char **vs
...
@@ -734,7 +734,7 @@ GetFieldRes hashTypeGetValue(redisDb *db, robj *o, sds field, unsigned char **vs
serverPanic
(
"Unknown hash encoding"
);
serverPanic
(
"Unknown hash encoding"
);
}
}
if
(
expiredAt
>=
(
uint64_t
)
commandTimeSnapshot
())
if
(
(
expiredAt
>=
(
uint64_t
)
commandTimeSnapshot
())
||
(
hfeFlags
&
HFE_LAZY_ACCESS_EXPIRED
))
return
GETF_OK
;
return
GETF_OK
;
if
(
server
.
masterhost
)
{
if
(
server
.
masterhost
)
{
...
@@ -1134,6 +1134,20 @@ int hashTypeSetExInit(robj *key, robj *o, client *c, redisDb *db, const char *cm
...
@@ -1134,6 +1134,20 @@ int hashTypeSetExInit(robj *key, robj *o, client *c, redisDb *db, const char *cm
dictEntry
*
de
=
dbFind
(
c
->
db
,
key
->
ptr
);
dictEntry
*
de
=
dbFind
(
c
->
db
,
key
->
ptr
);
serverAssert
(
de
!=
NULL
);
serverAssert
(
de
!=
NULL
);
lpt
->
key
=
dictGetKey
(
de
);
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
)
{
}
else
if
(
o
->
encoding
==
OBJ_ENCODING_HT
)
{
/* Take care dict has HFE metadata */
/* Take care dict has HFE metadata */
if
(
!
isDictWithMetaHFE
(
ht
))
{
if
(
!
isDictWithMetaHFE
(
ht
))
{
...
@@ -1151,6 +1165,18 @@ int hashTypeSetExInit(robj *key, robj *o, client *c, redisDb *db, const char *cm
...
@@ -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
->
key
=
dictGetKey
(
de
);
/* reference key in keyspace */
m
->
hfe
=
ebCreate
();
/* Allocate HFE DS */
m
->
hfe
=
ebCreate
();
/* Allocate HFE DS */
m
->
expireMeta
.
trash
=
1
;
/* mark as trash (as long it wasn't ebAdd()) */
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 @
30eb6c32
...
@@ -2,8 +2,13 @@
...
@@ -2,8 +2,13 @@
* Copyright (c) 2009-Present, Redis Ltd.
* Copyright (c) 2009-Present, Redis Ltd.
* All rights reserved.
* All rights reserved.
*
*
* Copyright (c) 2024-present, Valkey contributors.
* All rights reserved.
*
* Licensed under your choice of the Redis Source Available License 2.0
* Licensed under your choice of the Redis Source Available License 2.0
* (RSALv2) or the Server Side Public License v1 (SSPLv1).
* (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"
#include "server.h"
...
@@ -1492,6 +1497,7 @@ void sunionDiffGenericCommand(client *c, robj **setkeys, int setnum,
...
@@ -1492,6 +1497,7 @@ void sunionDiffGenericCommand(client *c, robj **setkeys, int setnum,
robj
**
sets
=
zmalloc
(
sizeof
(
robj
*
)
*
setnum
);
robj
**
sets
=
zmalloc
(
sizeof
(
robj
*
)
*
setnum
);
setTypeIterator
*
si
;
setTypeIterator
*
si
;
robj
*
dstset
=
NULL
;
robj
*
dstset
=
NULL
;
int
dstset_encoding
=
OBJ_ENCODING_INTSET
;
char
*
str
;
char
*
str
;
size_t
len
;
size_t
len
;
int64_t
llval
;
int64_t
llval
;
...
@@ -1510,6 +1516,23 @@ void sunionDiffGenericCommand(client *c, robj **setkeys, int setnum,
...
@@ -1510,6 +1516,23 @@ void sunionDiffGenericCommand(client *c, robj **setkeys, int setnum,
zfree
(
sets
);
zfree
(
sets
);
return
;
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
;
sets
[
j
]
=
setobj
;
if
(
j
>
0
&&
sets
[
0
]
==
sets
[
j
])
{
if
(
j
>
0
&&
sets
[
0
]
==
sets
[
j
])
{
sameset
=
1
;
sameset
=
1
;
...
@@ -1552,7 +1575,11 @@ void sunionDiffGenericCommand(client *c, robj **setkeys, int setnum,
...
@@ -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
/* 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
* 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*/
* this set object will be the resulting object to set into the target key*/
dstset
=
createIntsetObject
();
if
(
dstset_encoding
==
OBJ_ENCODING_INTSET
)
{
dstset
=
createIntsetObject
();
}
else
{
dstset
=
createSetObject
();
}
if
(
op
==
SET_OP_UNION
)
{
if
(
op
==
SET_OP_UNION
)
{
/* Union is trivial, just add every element of every set to the
/* Union is trivial, just add every element of every set to the
...
...
src/t_zset.c
View file @
30eb6c32
/*
/*
t_zset.c -- zset data type implementation.
*
Copyright (c) 2009-current, Redis Ltd.
*
* Copyright (c) 2009-
2012, Pieter Noordhuis <pcnoordhuis at gmail dot com>
* Copyright (c) 2009-
Present, Redis Ltd.
* All rights reserved.
* All rights reserved.
*
*
*
Redistribution and use in source and binary forms, with or without
*
Copyright (c) 2024-present, Valkey contributors.
*
modification, are permitted provided that the following conditions are met:
*
All rights reserved.
*
*
* * Redistributions of source code must retain the above copyright notice,
* Licensed under your choice of the Redis Source Available License 2.0
* this list of conditions and the following disclaimer.
* (RSALv2) or the Server Side Public License v1 (SSPLv1).
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of Redis nor the names of its contributors may be used
* to endorse or promote products derived from this software without
* specific prior written permission.
*
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* Portions of this file are available under BSD3 terms; see REDISCONTRIBUTIONS for more information.
* 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.
*/
*/
/*-----------------------------------------------------------------------------
/*-----------------------------------------------------------------------------
...
@@ -55,7 +39,7 @@
...
@@ -55,7 +39,7 @@
* c) there is a back pointer, so it's a doubly linked list with the back
* 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
* pointers being only at "level 1". This allows to traverse the list
* from tail to head, useful for ZREVRANGE. */
* from tail to head, useful for ZREVRANGE. */
#include "fast_float_strtod.h"
#include "server.h"
#include "server.h"
#include "intset.h"
/* Compact integer set structure */
#include "intset.h"
/* Compact integer set structure */
#include <math.h>
#include <math.h>
...
@@ -566,11 +550,11 @@ static int zslParseRange(robj *min, robj *max, zrangespec *spec) {
...
@@ -566,11 +550,11 @@ static int zslParseRange(robj *min, robj *max, zrangespec *spec) {
spec
->
min
=
(
long
)
min
->
ptr
;
spec
->
min
=
(
long
)
min
->
ptr
;
}
else
{
}
else
{
if
(((
char
*
)
min
->
ptr
)[
0
]
==
'('
)
{
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
;
if
(
eptr
[
0
]
!=
'\0'
||
isnan
(
spec
->
min
))
return
C_ERR
;
spec
->
minex
=
1
;
spec
->
minex
=
1
;
}
else
{
}
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
;
if
(
eptr
[
0
]
!=
'\0'
||
isnan
(
spec
->
min
))
return
C_ERR
;
}
}
}
}
...
@@ -578,11 +562,11 @@ static int zslParseRange(robj *min, robj *max, zrangespec *spec) {
...
@@ -578,11 +562,11 @@ static int zslParseRange(robj *min, robj *max, zrangespec *spec) {
spec
->
max
=
(
long
)
max
->
ptr
;
spec
->
max
=
(
long
)
max
->
ptr
;
}
else
{
}
else
{
if
(((
char
*
)
max
->
ptr
)[
0
]
==
'('
)
{
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
;
if
(
eptr
[
0
]
!=
'\0'
||
isnan
(
spec
->
max
))
return
C_ERR
;
spec
->
maxex
=
1
;
spec
->
maxex
=
1
;
}
else
{
}
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
;
if
(
eptr
[
0
]
!=
'\0'
||
isnan
(
spec
->
max
))
return
C_ERR
;
}
}
}
}
...
@@ -789,7 +773,7 @@ double zzlStrtod(unsigned char *vstr, unsigned int vlen) {
...
@@ -789,7 +773,7 @@ double zzlStrtod(unsigned char *vstr, unsigned int vlen) {
vlen
=
sizeof
(
buf
)
-
1
;
vlen
=
sizeof
(
buf
)
-
1
;
memcpy
(
buf
,
vstr
,
vlen
);
memcpy
(
buf
,
vstr
,
vlen
);
buf
[
vlen
]
=
'\0'
;
buf
[
vlen
]
=
'\0'
;
return
strtod
(
buf
,
NULL
);
return
fast_float_
strtod
(
buf
,
NULL
);
}
}
double
zzlGetScore
(
unsigned
char
*
sptr
)
{
double
zzlGetScore
(
unsigned
char
*
sptr
)
{
...
@@ -2611,16 +2595,6 @@ static void zdiff(zsetopsrc *src, long setnum, zset *dstzset, size_t *maxelelen,
...
@@ -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
/* The zunionInterDiffGenericCommand() function is called in order to implement the
* following commands: ZUNION, ZINTER, ZDIFF, ZUNIONSTORE, ZINTERSTORE, ZDIFFSTORE,
* following commands: ZUNION, ZINTER, ZDIFF, ZUNIONSTORE, ZINTERSTORE, ZDIFFSTORE,
* ZINTERCARD.
* ZINTERCARD.
...
@@ -2816,7 +2790,6 @@ void zunionInterDiffGenericCommand(client *c, robj *dstkey, int numkeysIndex, in
...
@@ -2816,7 +2790,6 @@ void zunionInterDiffGenericCommand(client *c, robj *dstkey, int numkeysIndex, in
zuiClearIterator
(
&
src
[
0
]);
zuiClearIterator
(
&
src
[
0
]);
}
}
}
else
if
(
op
==
SET_OP_UNION
)
{
}
else
if
(
op
==
SET_OP_UNION
)
{
dict
*
accumulator
=
dictCreate
(
&
setAccumulatorDictType
);
dictIterator
*
di
;
dictIterator
*
di
;
dictEntry
*
de
,
*
existing
;
dictEntry
*
de
,
*
existing
;
double
score
;
double
score
;
...
@@ -2824,7 +2797,7 @@ void zunionInterDiffGenericCommand(client *c, robj *dstkey, int numkeysIndex, in
...
@@ -2824,7 +2797,7 @@ void zunionInterDiffGenericCommand(client *c, robj *dstkey, int numkeysIndex, in
if
(
setnum
)
{
if
(
setnum
)
{
/* Our union is at least as large as the largest set.
/* Our union is at least as large as the largest set.
* Resize the dictionary ASAP to avoid useless rehashing. */
* 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
/* Step 1: Create a dictionary of elements -> aggregated-scores
...
@@ -2839,7 +2812,7 @@ void zunionInterDiffGenericCommand(client *c, robj *dstkey, int numkeysIndex, in
...
@@ -2839,7 +2812,7 @@ void zunionInterDiffGenericCommand(client *c, robj *dstkey, int numkeysIndex, in
if
(
isnan
(
score
))
score
=
0
;
if
(
isnan
(
score
))
score
=
0
;
/* Search for this element in the accumulating dictionary. */
/* 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 we don't have it, we need to create a new entry. */
if
(
!
existing
)
{
if
(
!
existing
)
{
tmp
=
zuiNewSdsFromValue
(
&
zval
);
tmp
=
zuiNewSdsFromValue
(
&
zval
);
...
@@ -2849,7 +2822,7 @@ void zunionInterDiffGenericCommand(client *c, robj *dstkey, int numkeysIndex, in
...
@@ -2849,7 +2822,7 @@ void zunionInterDiffGenericCommand(client *c, robj *dstkey, int numkeysIndex, in
totelelen
+=
sdslen
(
tmp
);
totelelen
+=
sdslen
(
tmp
);
if
(
sdslen
(
tmp
)
>
maxelelen
)
maxelelen
=
sdslen
(
tmp
);
if
(
sdslen
(
tmp
)
>
maxelelen
)
maxelelen
=
sdslen
(
tmp
);
/* Update the element with its initial score. */
/* Update the element with its initial score. */
dictSetKey
(
accumulator
,
de
,
tmp
);
dictSetKey
(
dstzset
->
dict
,
de
,
tmp
);
dictSetDoubleVal
(
de
,
score
);
dictSetDoubleVal
(
de
,
score
);
}
else
{
}
else
{
/* Update the score with the score of the new instance
/* Update the score with the score of the new instance
...
@@ -2866,21 +2839,15 @@ void zunionInterDiffGenericCommand(client *c, robj *dstkey, int numkeysIndex, in
...
@@ -2866,21 +2839,15 @@ void zunionInterDiffGenericCommand(client *c, robj *dstkey, int numkeysIndex, in
}
}
/* Step 2: convert the dictionary into the final sorted set. */
/* Step 2: convert the dictionary into the final sorted set. */
di
=
dictGetIterator
(
accumulator
);
di
=
dictGetIterator
(
dstzset
->
dict
);
/* 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
));
while
((
de
=
dictNext
(
di
))
!=
NULL
)
{
while
((
de
=
dictNext
(
di
))
!=
NULL
)
{
sds
ele
=
dictGetKey
(
de
);
sds
ele
=
dictGetKey
(
de
);
score
=
dictGetDoubleVal
(
de
);
score
=
dictGetDoubleVal
(
de
);
znode
=
zslInsert
(
dstzset
->
zsl
,
score
,
ele
);
znode
=
zslInsert
(
dstzset
->
zsl
,
score
,
ele
);
dict
Add
(
dstzset
->
dict
,
el
e
,
&
znode
->
score
);
dict
SetVal
(
dstzset
->
dict
,
d
e
,
&
znode
->
score
);
}
}
dictReleaseIterator
(
di
);
dictReleaseIterator
(
di
);
dictRelease
(
accumulator
);
}
else
if
(
op
==
SET_OP_DIFF
)
{
}
else
if
(
op
==
SET_OP_DIFF
)
{
zdiff
(
src
,
setnum
,
dstzset
,
&
maxelelen
,
&
totelelen
);
zdiff
(
src
,
setnum
,
dstzset
,
&
maxelelen
,
&
totelelen
);
}
else
{
}
else
{
...
...
src/util.c
View file @
30eb6c32
...
@@ -30,6 +30,7 @@
...
@@ -30,6 +30,7 @@
#include "fmacros.h"
#include "fmacros.h"
#include "fpconv_dtoa.h"
#include "fpconv_dtoa.h"
#include "fast_float_strtod.h"
#include <stdlib.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdio.h>
#include <string.h>
#include <string.h>
...
@@ -612,7 +613,7 @@ int string2ld(const char *s, size_t slen, long double *dp) {
...
@@ -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
)
{
int
string2d
(
const
char
*
s
,
size_t
slen
,
double
*
dp
)
{
errno
=
0
;
errno
=
0
;
char
*
eptr
;
char
*
eptr
;
*
dp
=
strtod
(
s
,
&
eptr
);
*
dp
=
fast_float_
strtod
(
s
,
&
eptr
);
if
(
slen
==
0
||
if
(
slen
==
0
||
isspace
(((
const
char
*
)
s
)[
0
])
||
isspace
(((
const
char
*
)
s
)[
0
])
||
(
size_t
)(
eptr
-
(
char
*
)
s
)
!=
slen
||
(
size_t
)(
eptr
-
(
char
*
)
s
)
!=
slen
||
...
...
tests/integration/corrupt-dump.tcl
View file @
30eb6c32
...
@@ -897,5 +897,39 @@ test {corrupt payload: fuzzer findings - set with invalid length causes smembers
...
@@ -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
}
;
# tags
tests/integration/redis-cli.tcl
View file @
30eb6c32
...
@@ -808,6 +808,12 @@ if {!$::tls} { ;# fake_redis_node doesn't support TLS
...
@@ -808,6 +808,12 @@ if {!$::tls} { ;# fake_redis_node doesn't support TLS
assert_equal 3
[
exec
{*}
$cmdline
ZCARD new_zset
]
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
]
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"
}}
{
start_server
{
tags
{
"cli external:skip"
}}
{
...
...
tests/modules/hash.c
View file @
30eb6c32
...
@@ -3,6 +3,8 @@
...
@@ -3,6 +3,8 @@
#include <errno.h>
#include <errno.h>
#include <stdlib.h>
#include <stdlib.h>
#define UNUSED(x) (void)(x)
/* If a string is ":deleted:", the special value for deleted hash fields is
/* If a string is ":deleted:", the special value for deleted hash fields is
* returned; otherwise the input string is returned. */
* returned; otherwise the input string is returned. */
static
RedisModuleString
*
value_or_delete
(
RedisModuleString
*
s
)
{
static
RedisModuleString
*
value_or_delete
(
RedisModuleString
*
s
)
{
...
@@ -76,15 +78,100 @@ int hash_set(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
...
@@ -76,15 +78,100 @@ int hash_set(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
return
RedisModule_ReplyWithLongLong
(
ctx
,
result
);
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
)
{
int
RedisModule_OnLoad
(
RedisModuleCtx
*
ctx
,
RedisModuleString
**
argv
,
int
argc
)
{
REDISMODULE_NOT_USED
(
argv
);
REDISMODULE_NOT_USED
(
argv
);
REDISMODULE_NOT_USED
(
argc
);
REDISMODULE_NOT_USED
(
argc
);
if
(
RedisModule_Init
(
ctx
,
"hash"
,
1
,
REDISMODULE_APIVER_1
)
==
if
(
RedisModule_Init
(
ctx
,
"hash"
,
1
,
REDISMODULE_APIVER_1
)
==
REDISMODULE_ERR
)
REDISMODULE_OK
&&
RedisModule_CreateCommand
(
ctx
,
"hash.set"
,
hash_set
,
"write"
,
1
,
1
,
1
)
==
REDISMODULE_OK
)
{
return
REDISMODULE_OK
;
}
else
{
return
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 @
30eb6c32
...
@@ -51,6 +51,26 @@ start_server {tags {"introspection"}} {
...
@@ -51,6 +51,26 @@ start_server {tags {"introspection"}} {
assert_morethan $newlru $oldlru
assert_morethan $newlru $oldlru
}
{}
{
needs:debug
}
}
{}
{
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
}
{
test
{
TOUCH returns the number of existing keys specified
}
{
r flushdb
r flushdb
r set key1
{
t
}
1
r set key1
{
t
}
1
...
...
tests/unit/moduleapi/hash.tcl
View file @
30eb6c32
...
@@ -60,6 +60,39 @@ start_server {tags {"modules"}} {
...
@@ -60,6 +60,39 @@ start_server {tags {"modules"}} {
r debug set-active-expire 1
r debug set-active-expire 1
}
{
OK
}
{
needs:debug
}
}
{
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"
{
test
"Unload the module - hash"
{
assert_equal
{
OK
}
[
r module unload hash
]
assert_equal
{
OK
}
[
r module unload hash
]
}
}
...
...
tests/unit/type/hash-field-expire.tcl
View file @
30eb6c32
...
@@ -199,12 +199,12 @@ start_server {tags {"external:skip needs:debug"}} {
...
@@ -199,12 +199,12 @@ start_server {tags {"external:skip needs:debug"}} {
set hash_sizes
{
1 15 16 17 31 32 33 40
}
set hash_sizes
{
1 15 16 17 31 32 33 40
}
foreach h $hash_sizes
{
foreach h $hash_sizes
{
for
{
set i 1
}
{
$i
<= $h
}
{
incr i
}
{
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 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
]
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 hset same$h f$i v$i
r hpexpire same$h 100 FIELDS 1 f$i
r hpexpire same$h 100 FIELDS 1 f$i
assert_equal 1
[
r HEXISTS same$h f$i
]
assert_equal 1
[
r HEXISTS same$h f$i
]
...
@@ -286,10 +286,9 @@ start_server {tags {"external:skip needs:debug"}} {
...
@@ -286,10 +286,9 @@ start_server {tags {"external:skip needs:debug"}} {
test
"HEXPIRETIME - returns TTL in Unix timestamp (
$type
)"
{
test
"HEXPIRETIME - returns TTL in Unix timestamp (
$type
)"
{
r del myhash
r del myhash
r HSET myhash field1 value1
r HSET myhash field1 value1
r HPEXPIRE myhash 1000 NX FIELDS 1 field1
set lo
[
expr
{[
clock seconds
]
+ 1
}]
set lo
[
expr
{[
clock seconds
]
+ 1
}]
set hi
[
expr
{[
clock seconds
]
+ 2
}]
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 HEXPIRETIME myhash FIELDS 1 field1
]
$lo $hi
assert_range
[
r HPEXPIRETIME myhash FIELDS 1 field1
]
[
expr $lo*1000
]
[
expr $hi*1000
]
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"}} {
...
@@ -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"
}
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
)"
{
test
"MOVE to another DB hash with fields to be expired (
$type
)"
{
r select 9
r select 9
r flushall
r flushall
...
@@ -642,6 +655,20 @@ start_server {tags {"external:skip needs:debug"}} {
...
@@ -642,6 +655,20 @@ start_server {tags {"external:skip needs:debug"}} {
}
{}
{
singledb:skip
}
}
{}
{
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
)"
{
test
"Test SWAPDB hash-fields to be expired (
$type
)"
{
r select 9
r select 9
r flushall
r flushall
...
@@ -663,6 +690,29 @@ start_server {tags {"external:skip needs:debug"}} {
...
@@ -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"
}
wait_for_condition 20 10
{
[
r exists myhash
]
== 0
}
else
{
fail
"'myhash' should be expired"
}
}
{}
{
singledb:skip
}
}
{}
{
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
)"
{
test
"HMGET - returns empty entries if fields or hash expired (
$type
)"
{
r debug set-active-expire 0
r debug set-active-expire 0
r del h1 h2
r del h1 h2
...
@@ -731,6 +781,20 @@ start_server {tags {"external:skip needs:debug"}} {
...
@@ -731,6 +781,20 @@ start_server {tags {"external:skip needs:debug"}} {
assert_equal
[
r hexpiretime myhash FIELDS 3 a b c
]
{
2524600800 2524600801 -1
}
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
}
{
test
{
DUMP / RESTORE are able to serialize / unserialize a hash with TTL 0 for all fields
}
{
r config set sanitize-dump-payload yes
r config set sanitize-dump-payload yes
r del myhash
r del myhash
...
@@ -950,6 +1014,7 @@ start_server {tags {"external:skip needs:debug"}} {
...
@@ -950,6 +1014,7 @@ start_server {tags {"external:skip needs:debug"}} {
start_server
{
overrides
{
appendonly
{
yes
}
appendfsync always
}
tags
{
external:skip
}}
{
start_server
{
overrides
{
appendonly
{
yes
}
appendfsync always
}
tags
{
external:skip
}}
{
set aof
[
get_last_incr_aof_path r
]
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
# Time is in the past so it should propagate HDELs to replica
# and delete the fields
# and delete the fields
...
@@ -976,6 +1041,7 @@ start_server {tags {"external:skip needs:debug"}} {
...
@@ -976,6 +1041,7 @@ start_server {tags {"external:skip needs:debug"}} {
r hpexpireat h2
[
expr
[
clock seconds
]
*1000+100000
]
LT FIELDS 1 f3
r hpexpireat h2
[
expr
[
clock seconds
]
*1000+100000
]
LT FIELDS 1 f3
r hexpireat h2
[
expr
[
clock seconds
]
+10
]
NX FIELDS 1 f4
r hexpireat h2
[
expr
[
clock seconds
]
+10
]
NX FIELDS 1 f4
r debug set-active-expire 1
wait_for_condition 50 100
{
wait_for_condition 50 100
{
[
r hlen h2
]
eq 2
[
r hlen h2
]
eq 2
}
else
{
}
else
{
...
@@ -1004,7 +1070,7 @@ start_server {tags {"external:skip needs:debug"}} {
...
@@ -1004,7 +1070,7 @@ start_server {tags {"external:skip needs:debug"}} {
{
hdel h2 f2
}
{
hdel h2 f2
}
}
}
}
}
}
}
{}
{
needs:debug
}
test
"Lazy Expire - fields are lazy deleted and propagated to replicas (
$type
)"
{
test
"Lazy Expire - fields are lazy deleted and propagated to replicas (
$type
)"
{
start_server
{
overrides
{
appendonly
{
yes
}
appendfsync always
}
tags
{
external:skip
}}
{
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