add listpack support

This commit is contained in:
Ozan Tezcan 2024-04-15 06:22:23 +03:00
parent ffac5a7d80
commit 83ba78965f
12 changed files with 1680 additions and 772 deletions

View File

@ -1944,7 +1944,7 @@ static int rioWriteHashIteratorCursor(rio *r, hashTypeIterator *hi, int what) {
unsigned int vlen = UINT_MAX;
long long vll = LLONG_MAX;
hashTypeCurrentFromListpack(hi, what, &vstr, &vlen, &vll);
hashTypeCurrentFromListpack(hi, what, &vstr, &vlen, &vll, NULL);
if (vstr)
return rioWriteBulkString(r, (char*)vstr, vlen);
else

View File

@ -345,8 +345,7 @@ robj *dbRandomKey(redisDb *db) {
valobj = dictGetVal(de);
/* Special care to hash that all its fields got expired */
if (valobj->type == OBJ_HASH && valobj->encoding == OBJ_ENCODING_HT &&
hashTypeIsEmpty(valobj)) {
if (valobj->type == OBJ_HASH && hashTypeIsEmpty(valobj)) {
if (--maxtries) {
expireIfNeeded(db, keyobj, 0, valobj);
decrRefCount(keyobj);
@ -1219,9 +1218,7 @@ void scanGenericCommand(client *c, robj *o, unsigned long long cursor) {
}
setTypeReleaseIterator(si);
cursor = 0;
} else if ((o->type == OBJ_HASH || o->type == OBJ_ZSET) &&
o->encoding == OBJ_ENCODING_LISTPACK)
{
} else if (o->type == OBJ_ZSET && o->encoding == OBJ_ENCODING_LISTPACK) {
unsigned char *p = lpFirst(o->ptr);
unsigned char *str;
int64_t len;
@ -1246,6 +1243,47 @@ void scanGenericCommand(client *c, robj *o, unsigned long long cursor) {
p = lpNext(o->ptr, p);
}
cursor = 0;
} else if (o->type == OBJ_HASH &&
(o->encoding == OBJ_ENCODING_LISTPACK ||
o->encoding == OBJ_ENCODING_LISTPACK_TTL))
{
int expired;
int64_t len;
long long expireAt;
unsigned char *lp = hashTypeListpackGetLp(o);
unsigned char *p = lpFirst(lp);
unsigned char *str, *val;
unsigned char intbuf[LP_INTBUF_SIZE];
while(p) {
expired = 0;
str = lpGet(p, &len, intbuf);
p = lpNext(lp, p);
val = p; /* Keep pointer to value */
if (o->encoding == OBJ_ENCODING_LISTPACK_TTL) {
p = lpNext(lp, p);
lpGetValue(p, NULL, &expireAt);
expired = hashTypeListpackIsExpired(expireAt);
}
if (expired || (use_pattern && !stringmatchlen(pat, sdslen(pat), (char *)str, len, 0))) {
/* jump to the next key/val pair */
p = lpNext(lp, p);
continue;
}
/* add key object */
listAddNodeTail(keys, sdsnewlen(str, len));
/* add value object */
if (!no_values) {
str = lpGet(val, &len, intbuf);
listAddNodeTail(keys, sdsnewlen(str, len));
}
p = lpNext(lp, p);
}
cursor = 0;
} else {
serverPanic("Not handled encoding in SCAN.");
}
@ -1414,7 +1452,7 @@ void renameGenericCommand(client *c, int nx) {
/* If hash with expiration on fields then remove it from global HFE DS and
* keep next expiration time. Otherwise, dbDelete() will remove it from the
* global HFE DS and we will lose the expiration time. */
if (o->type == OBJ_HASH && o->encoding == OBJ_ENCODING_HT)
if (o->type == OBJ_HASH)
minHashExpireTime = hashTypeRemoveFromExpires(&c->db->hexpires, o);
dbDelete(c->db,c->argv[1]);
@ -1493,7 +1531,7 @@ void moveCommand(client *c) {
/* If hash with expiration on fields, remove it from global HFE DS and keep
* aside registered expiration time. Must be before deletion of the object.
* hexpires (ebuckets) embed in stored items its structure. */
if (o->type == OBJ_HASH && o->encoding == OBJ_ENCODING_HT)
if (o->type == OBJ_HASH)
hashExpireTime = hashTypeRemoveFromExpires(&src->hexpires, o);
incrRefCount(o);

View File

@ -1408,15 +1408,19 @@ static inline void lpSaveValue(unsigned char *val, unsigned int len, int64_t lva
/* Randomly select a pair of key and value.
* total_count is a pre-computed length/2 of the listpack (to avoid calls to lpLength)
* 'key' and 'val' are used to store the result key value pair.
* 'val' can be NULL if the value is not needed. */
void lpRandomPair(unsigned char *lp, unsigned long total_count, listpackEntry *key, listpackEntry *val) {
* 'val' can be NULL if the value is not needed.
* 'tuple_len' indicates entry count of a single logical item. It should be 2
* if listpack was saved as key-value pair or more for key-value-...(n_entries). */
void lpRandomPair(unsigned char *lp, unsigned long total_count, listpackEntry *key, listpackEntry *val, int tuple_len) {
unsigned char *p;
assert(tuple_len >= 2);
/* Avoid div by zero on corrupt listpack */
assert(total_count);
/* Generate even numbers, because listpack saved K-V pair */
int r = (rand() % total_count) * 2;
/* Generate index that keys exist at, because listpack saved as tuples that contain entries K-V-... */
int r = (rand() % total_count) * tuple_len;
assert((p = lpSeek(lp, r)));
key->sval = lpGetValue(p, &(key->slen), &(key->lval));
@ -1466,26 +1470,30 @@ void lpRandomEntries(unsigned char *lp, unsigned int count, listpackEntry *entri
/* Randomly select count of key value pairs and store into 'keys' and
* 'vals' args. The order of the picked entries is random, and the selections
* are non-unique (repetitions are possible).
* The 'vals' arg can be NULL in which case we skip these. */
void lpRandomPairs(unsigned char *lp, unsigned int count, listpackEntry *keys, listpackEntry *vals) {
* The 'vals' arg can be NULL in which case we skip these.
* 'tuple_len' indicates entry count of a single logical item. It should be 2
* if listpack was saved as key-value pair or more for key-value-...(n_entries). */
void lpRandomPairs(unsigned char *lp, unsigned int count, listpackEntry *keys, listpackEntry *vals, int tuple_len) {
unsigned char *p, *key, *value;
unsigned int klen = 0, vlen = 0;
long long klval = 0, vlval = 0;
assert(tuple_len >= 2);
/* Notice: the index member must be first due to the use in uintCompare */
typedef struct {
unsigned int index;
unsigned int order;
} rand_pick;
rand_pick *picks = lp_malloc(sizeof(rand_pick)*count);
unsigned int total_size = lpLength(lp)/2;
unsigned int total_size = lpLength(lp)/tuple_len;
/* Avoid div by zero on corrupt listpack */
assert(total_size);
/* create a pool of random indexes (some may be duplicate). */
for (unsigned int i = 0; i < count; i++) {
picks[i].index = (rand() % total_size) * 2; /* Generate even indexes */
picks[i].index = (rand() % total_size) * tuple_len; /* Generate indexes that key exist at */
/* keep track of the order we picked them */
picks[i].order = i;
}
@ -1507,8 +1515,11 @@ void lpRandomPairs(unsigned char *lp, unsigned int count, listpackEntry *keys, l
lpSaveValue(value, vlen, vlval, &vals[storeorder]);
pickindex++;
}
lpindex += 2;
p = lpNext(lp, p);
lpindex += tuple_len;
for (int i = 0; i < tuple_len - 1; i++) {
p = lpNext(lp, p);
}
}
lp_free(picks);
@ -1518,13 +1529,17 @@ void lpRandomPairs(unsigned char *lp, unsigned int count, listpackEntry *keys, l
* 'vals' args. The selections are unique (no repetitions), and the order of
* the picked entries is NOT-random.
* The 'vals' arg can be NULL in which case we skip these.
* 'tuple_len' indicates entry count of a single logical item. It should be 2
* if listpack was saved as key-value pair or more for key-value-...(n_entries).
* The return value is the number of items picked which can be lower than the
* requested count if the listpack doesn't hold enough pairs. */
unsigned int lpRandomPairsUnique(unsigned char *lp, unsigned int count, listpackEntry *keys, listpackEntry *vals) {
unsigned int lpRandomPairsUnique(unsigned char *lp, unsigned int count, listpackEntry *keys, listpackEntry *vals, int tuple_len) {
assert(tuple_len >= 2);
unsigned char *p, *key;
unsigned int klen = 0;
long long klval = 0;
unsigned int total_size = lpLength(lp)/2;
unsigned int total_size = lpLength(lp)/tuple_len;
unsigned int index = 0;
if (count > total_size)
count = total_size;
@ -1532,7 +1547,7 @@ unsigned int lpRandomPairsUnique(unsigned char *lp, unsigned int count, listpack
p = lpFirst(lp);
unsigned int picked = 0, remaining = count;
while (picked < count && p) {
assert((p = lpNextRandom(lp, p, &index, remaining, 1)));
assert((p = lpNextRandom(lp, p, &index, remaining, tuple_len)));
key = lpGetValue(p, &klen, &klval);
lpSaveValue(key, klen, klval, &keys[picked]);
assert((p = lpNext(lp, p)));
@ -1554,8 +1569,9 @@ unsigned int lpRandomPairsUnique(unsigned char *lp, unsigned int count, listpack
* the end of the list. The 'index' needs to be initialized according to the
* current zero-based index matching the position of the starting element 'p'
* and is updated to match the returned element's zero-based index. If
* 'even_only' is nonzero, an element with an even index is picked, which is
* useful if the listpack represents a key-value pair sequence.
* 'tuple_len' indicates entry count of a single logical item. e.g. This is
* useful if listpack represents key-value pairs. In this case, tuple_len should
* be two and even indexes will be picked.
*
* Note that this function can return p. In order to skip the previously
* returned element, you need to call lpNext() or lpDelete() after each call to
@ -1565,7 +1581,7 @@ unsigned int lpRandomPairsUnique(unsigned char *lp, unsigned int count, listpack
* p = lpFirst(lp);
* i = 0;
* while (remaining > 0) {
* p = lpNextRandom(lp, p, &i, remaining--, 0);
* p = lpNextRandom(lp, p, &i, remaining--, 1);
*
* // ... Do stuff with p ...
*
@ -1574,8 +1590,9 @@ unsigned int lpRandomPairsUnique(unsigned char *lp, unsigned int count, listpack
* }
*/
unsigned char *lpNextRandom(unsigned char *lp, unsigned char *p, unsigned int *index,
unsigned int remaining, int even_only)
unsigned int remaining, int tuple_len)
{
assert(tuple_len > 0);
/* To only iterate once, every time we try to pick a member, the probability
* we pick it is the quotient of the count left we want to pick and the
* count still we haven't visited. This way, we could make every member be
@ -1583,15 +1600,14 @@ unsigned char *lpNextRandom(unsigned char *lp, unsigned char *p, unsigned int *i
unsigned int i = *index;
unsigned int total_size = lpLength(lp);
while (i < total_size && p != NULL) {
if (even_only && i % 2 != 0) {
if (i % tuple_len != 0) {
p = lpNext(lp, p);
i++;
continue;
}
/* Do we pick this element? */
unsigned int available = total_size - i;
if (even_only) available /= 2;
unsigned int available = (total_size - i) / tuple_len;
double randomDouble = ((double)rand()) / RAND_MAX;
double threshold = ((double)remaining) / available;
if (randomDouble <= threshold) {
@ -2210,7 +2226,7 @@ int listpackTest(int argc, char *argv[], int flags) {
unsigned index = 0;
while (remaining > 0) {
assert(p != NULL);
p = lpNextRandom(lp, p, &index, remaining--, 0);
p = lpNextRandom(lp, p, &index, remaining--, 1);
assert(p != NULL);
assert(p != prev);
prev = p;
@ -2226,7 +2242,7 @@ int listpackTest(int argc, char *argv[], int flags) {
unsigned i = 0;
/* Pick from empty listpack returns NULL. */
assert(lpNextRandom(lp, NULL, &i, 2, 0) == NULL);
assert(lpNextRandom(lp, NULL, &i, 2, 1) == NULL);
/* Add some elements and find their pointers within the listpack. */
lp = lpAppend(lp, (unsigned char *)"abc", 3);
@ -2239,19 +2255,19 @@ int listpackTest(int argc, char *argv[], int flags) {
assert(lpNext(lp, p2) == NULL);
/* Pick zero elements returns NULL. */
i = 0; assert(lpNextRandom(lp, lpFirst(lp), &i, 0, 0) == NULL);
i = 0; assert(lpNextRandom(lp, lpFirst(lp), &i, 0, 1) == NULL);
/* Pick all returns all. */
i = 0; assert(lpNextRandom(lp, p0, &i, 3, 0) == p0 && i == 0);
i = 1; assert(lpNextRandom(lp, p1, &i, 2, 0) == p1 && i == 1);
i = 2; assert(lpNextRandom(lp, p2, &i, 1, 0) == p2 && i == 2);
i = 0; assert(lpNextRandom(lp, p0, &i, 3, 1) == p0 && i == 0);
i = 1; assert(lpNextRandom(lp, p1, &i, 2, 1) == p1 && i == 1);
i = 2; assert(lpNextRandom(lp, p2, &i, 1, 1) == p2 && i == 2);
/* Pick more than one when there's only one left returns the last one. */
i = 2; assert(lpNextRandom(lp, p2, &i, 42, 0) == p2 && i == 2);
i = 2; assert(lpNextRandom(lp, p2, &i, 42, 1) == p2 && i == 2);
/* Pick all even elements returns p0 and p2. */
i = 0; assert(lpNextRandom(lp, p0, &i, 10, 1) == p0 && i == 0);
i = 1; assert(lpNextRandom(lp, p1, &i, 10, 1) == p2 && i == 2);
i = 0; assert(lpNextRandom(lp, p0, &i, 10, 2) == p0 && i == 0);
i = 1; assert(lpNextRandom(lp, p1, &i, 10, 2) == p2 && i == 2);
/* Don't crash even for bad index. */
for (int j = 0; j < 100; j++) {
@ -2264,7 +2280,7 @@ int listpackTest(int argc, char *argv[], int flags) {
}
i = j % 7;
unsigned int remaining = j % 5;
p = lpNextRandom(lp, p, &i, remaining, 0);
p = lpNextRandom(lp, p, &i, remaining, 1);
assert(p == p0 || p == p1 || p == p2 || p == NULL);
}
lpFree(lp);
@ -2275,7 +2291,7 @@ int listpackTest(int argc, char *argv[], int flags) {
unsigned char *lp = lpNew(0);
lp = lpAppend(lp, (unsigned char*)"abc", 3);
lp = lpAppend(lp, (unsigned char*)"123", 3);
lpRandomPair(lp, 1, &key, &val);
lpRandomPair(lp, 1, &key, &val, 2);
assert(memcmp(key.sval, "abc", key.slen) == 0);
assert(val.lval == 123);
lpFree(lp);
@ -2288,7 +2304,7 @@ int listpackTest(int argc, char *argv[], int flags) {
lp = lpAppend(lp, (unsigned char*)"123", 3);
lp = lpAppend(lp, (unsigned char*)"456", 3);
lp = lpAppend(lp, (unsigned char*)"def", 3);
lpRandomPair(lp, 2, &key, &val);
lpRandomPair(lp, 2, &key, &val, 2);
if (key.sval) {
assert(!memcmp(key.sval, "abc", key.slen));
assert(key.slen == 3);
@ -2301,6 +2317,39 @@ int listpackTest(int argc, char *argv[], int flags) {
lpFree(lp);
}
TEST("Random pair with tuple_len 3") {
listpackEntry key, val;
unsigned char *lp = lpNew(0);
lp = lpAppend(lp, (unsigned char*)"abc", 3);
lp = lpAppend(lp, (unsigned char*)"123", 3);
lp = lpAppend(lp, (unsigned char*)"xxx", 3);
lp = lpAppend(lp, (unsigned char*)"456", 3);
lp = lpAppend(lp, (unsigned char*)"def", 3);
lp = lpAppend(lp, (unsigned char*)"xxx", 3);
lp = lpAppend(lp, (unsigned char*)"fgh", 3);
lp = lpAppend(lp, (unsigned char*)"789", 3);
lp = lpAppend(lp, (unsigned char*)"xxx", 3);
lpRandomPair(lp, 3, &key, &val, 3);
if (key.sval) {
if (!memcmp(key.sval, "abc", key.slen)) {
assert(key.slen == 3);
assert(val.lval == 123);
} else if (!memcmp(key.sval, "fgh", key.slen)) {
assert(key.slen == 3);
assert(val.lval == 789);
} else {
assert(0);
};
}
if (!key.sval) {
assert(key.lval == 456);
assert(!memcmp(val.sval, "def", val.slen));
}
lpFree(lp);
}
TEST("Random pairs with one element") {
int count = 5;
unsigned char *lp = lpNew(0);
@ -2309,7 +2358,7 @@ int listpackTest(int argc, char *argv[], int flags) {
lp = lpAppend(lp, (unsigned char*)"abc", 3);
lp = lpAppend(lp, (unsigned char*)"123", 3);
lpRandomPairs(lp, count, keys, vals);
lpRandomPairs(lp, count, keys, vals, 2);
assert(memcmp(keys[4].sval, "abc", keys[4].slen) == 0);
assert(vals[4].lval == 123);
zfree(keys);
@ -2327,7 +2376,7 @@ int listpackTest(int argc, char *argv[], int flags) {
lp = lpAppend(lp, (unsigned char*)"123", 3);
lp = lpAppend(lp, (unsigned char*)"456", 3);
lp = lpAppend(lp, (unsigned char*)"def", 3);
lpRandomPairs(lp, count, keys, vals);
lpRandomPairs(lp, count, keys, vals, 2);
for (int i = 0; i < count; i++) {
if (keys[i].sval) {
assert(!memcmp(keys[i].sval, "abc", keys[i].slen));
@ -2344,6 +2393,46 @@ int listpackTest(int argc, char *argv[], int flags) {
lpFree(lp);
}
TEST("Random pairs with many elements and tuple_len 3") {
int count = 5;
lp = lpNew(0);
listpackEntry *keys = zcalloc(sizeof(listpackEntry) * count);
listpackEntry *vals = zcalloc(sizeof(listpackEntry) * count);
lp = lpAppend(lp, (unsigned char*)"abc", 3);
lp = lpAppend(lp, (unsigned char*)"123", 3);
lp = lpAppend(lp, (unsigned char*)"xxx", 3);
lp = lpAppend(lp, (unsigned char*)"456", 3);
lp = lpAppend(lp, (unsigned char*)"def", 3);
lp = lpAppend(lp, (unsigned char*)"xxx", 3);
lp = lpAppend(lp, (unsigned char*)"fgh", 3);
lp = lpAppend(lp, (unsigned char*)"789", 3);
lp = lpAppend(lp, (unsigned char*)"xxx", 3);
lpRandomPairs(lp, count, keys, vals, 3);
for (int i = 0; i < count; i++) {
if (keys[i].sval) {
if (!memcmp(keys[i].sval, "abc", keys[i].slen)) {
assert(keys[i].slen == 3);
assert(vals[i].lval == 123);
} else if (!memcmp(keys[i].sval, "fgh", keys[i].slen)) {
assert(keys[i].slen == 3);
assert(vals[i].lval == 789);
} else {
assert(0);
};
}
if (!keys[i].sval) {
assert(keys[i].lval == 456);
assert(!memcmp(vals[i].sval, "def", vals[i].slen));
}
}
zfree(keys);
zfree(vals);
lpFree(lp);
}
TEST("Random pairs unique with one element") {
unsigned picked;
int count = 5;
@ -2353,7 +2442,7 @@ int listpackTest(int argc, char *argv[], int flags) {
lp = lpAppend(lp, (unsigned char*)"abc", 3);
lp = lpAppend(lp, (unsigned char*)"123", 3);
picked = lpRandomPairsUnique(lp, count, keys, vals);
picked = lpRandomPairsUnique(lp, count, keys, vals, 2);
assert(picked == 1);
assert(memcmp(keys[0].sval, "abc", keys[0].slen) == 0);
assert(vals[0].lval == 123);
@ -2373,7 +2462,7 @@ int listpackTest(int argc, char *argv[], int flags) {
lp = lpAppend(lp, (unsigned char*)"123", 3);
lp = lpAppend(lp, (unsigned char*)"456", 3);
lp = lpAppend(lp, (unsigned char*)"def", 3);
picked = lpRandomPairsUnique(lp, count, keys, vals);
picked = lpRandomPairsUnique(lp, count, keys, vals, 2);
assert(picked == 2);
for (int i = 0; i < 2; i++) {
if (keys[i].sval) {
@ -2391,6 +2480,46 @@ int listpackTest(int argc, char *argv[], int flags) {
lpFree(lp);
}
TEST("Random pairs unique with many elements and tuple_len 3") {
unsigned picked;
int count = 5;
lp = lpNew(0);
listpackEntry *keys = zmalloc(sizeof(listpackEntry) * count);
listpackEntry *vals = zmalloc(sizeof(listpackEntry) * count);
lp = lpAppend(lp, (unsigned char*)"abc", 3);
lp = lpAppend(lp, (unsigned char*)"123", 3);
lp = lpAppend(lp, (unsigned char*)"xxx", 3);
lp = lpAppend(lp, (unsigned char*)"456", 3);
lp = lpAppend(lp, (unsigned char*)"def", 3);
lp = lpAppend(lp, (unsigned char*)"xxx", 3);
lp = lpAppend(lp, (unsigned char*)"fgh", 3);
lp = lpAppend(lp, (unsigned char*)"789", 3);
lp = lpAppend(lp, (unsigned char*)"xxx", 3);
picked = lpRandomPairsUnique(lp, count, keys, vals, 3);
assert(picked == 3);
for (int i = 0; i < 3; i++) {
if (keys[i].sval) {
if (!memcmp(keys[i].sval, "abc", keys[i].slen)) {
assert(keys[i].slen == 3);
assert(vals[i].lval == 123);
} else if (!memcmp(keys[i].sval, "fgh", keys[i].slen)) {
assert(keys[i].slen == 3);
assert(vals[i].lval == 789);
} else {
assert(0);
};
}
if (!keys[i].sval) {
assert(keys[i].lval == 456);
assert(!memcmp(vals[i].sval, "def", vals[i].slen));
}
}
zfree(keys);
zfree(vals);
lpFree(lp);
}
TEST("push various encodings") {
lp = lpNew(0);

View File

@ -69,12 +69,12 @@ int lpValidateIntegrity(unsigned char *lp, size_t size, int deep,
unsigned char *lpValidateFirst(unsigned char *lp);
int lpValidateNext(unsigned char *lp, unsigned char **pp, size_t lpbytes);
unsigned int lpCompare(unsigned char *p, unsigned char *s, uint32_t slen);
void lpRandomPair(unsigned char *lp, unsigned long total_count, listpackEntry *key, listpackEntry *val);
void lpRandomPairs(unsigned char *lp, unsigned int count, listpackEntry *keys, listpackEntry *vals);
unsigned int lpRandomPairsUnique(unsigned char *lp, unsigned int count, listpackEntry *keys, listpackEntry *vals);
void lpRandomPair(unsigned char *lp, unsigned long total_count, listpackEntry *key, listpackEntry *val, int tuple_len);
void lpRandomPairs(unsigned char *lp, unsigned int count, listpackEntry *keys, listpackEntry *vals, int tuple_len);
unsigned int lpRandomPairsUnique(unsigned char *lp, unsigned int count, listpackEntry *keys, listpackEntry *vals, int tuple_len);
void lpRandomEntries(unsigned char *lp, unsigned int count, listpackEntry *entries);
unsigned char *lpNextRandom(unsigned char *lp, unsigned char *p, unsigned int *index,
unsigned int remaining, int even_only);
unsigned int remaining, int tuple_len);
int lpSafeToAdd(unsigned char* lp, size_t add);
void lpRepr(unsigned char *lp);

View File

@ -5295,7 +5295,7 @@ int RM_HashSet(RedisModuleKey *key, int flags, ...) {
low_flags |= HASH_SET_TAKE_FIELD;
robj *argv[2] = {field,value};
hashTypeTryConversion(key->value,argv,0,1);
hashTypeTryConversion(key->db, key->value,argv,0,1);
int updated = hashTypeSet(key->db, key->value, field->ptr, value->ptr, low_flags);
count += (flags & REDISMODULE_HASH_COUNT_ALL) ? 1 : updated;

View File

@ -333,17 +333,7 @@ void freeZsetObject(robj *o) {
}
void freeHashObject(robj *o) {
switch (o->encoding) {
case OBJ_ENCODING_HT:
dictRelease((dict*) o->ptr);
break;
case OBJ_ENCODING_LISTPACK:
lpFree(o->ptr);
break;
default:
serverPanic("Unknown hash encoding type");
break;
}
hashTypeFree(o);
}
void freeModuleObject(robj *o) {

View File

@ -2070,7 +2070,7 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, sds key, int dbid, int *error) {
/* Too many entries? Use a hash table right from the start. */
if (len > server.hash_max_listpack_entries)
hashTypeConvert(o, OBJ_ENCODING_HT);
hashTypeConvert(NULL, o, OBJ_ENCODING_HT);
else if (deep_integrity_validation) {
/* In this mode, we need to guarantee that the server won't crash
* later when the ziplist is converted to a dict.
@ -2115,7 +2115,7 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, sds key, int dbid, int *error) {
sdslen(value) > server.hash_max_listpack_value ||
!lpSafeToAdd(o->ptr, hfieldlen(field) + sdslen(value)))
{
hashTypeConvert(o, OBJ_ENCODING_HT);
hashTypeConvert(NULL, o, OBJ_ENCODING_HT);
dictUseStoredKeyApi((dict *)o->ptr, 1);
ret = dictAdd((dict*)o->ptr, field, value);
dictUseStoredKeyApi((dict *)o->ptr, 0);
@ -2331,7 +2331,7 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, sds key, int dbid, int *error) {
if (hashTypeLength(o, 0) > server.hash_max_listpack_entries ||
maxlen > server.hash_max_listpack_value)
{
hashTypeConvert(o, OBJ_ENCODING_HT);
hashTypeConvert(NULL, o, OBJ_ENCODING_HT);
}
}
break;
@ -2468,7 +2468,7 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, sds key, int dbid, int *error) {
}
if (hashTypeLength(o, 0) > server.hash_max_listpack_entries)
hashTypeConvert(o, OBJ_ENCODING_HT);
hashTypeConvert(NULL, o, OBJ_ENCODING_HT);
else
o->ptr = lpShrinkToFit(o->ptr);
break;
@ -2490,7 +2490,7 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, sds key, int dbid, int *error) {
}
if (hashTypeLength(o, 0) > server.hash_max_listpack_entries)
hashTypeConvert(o, OBJ_ENCODING_HT);
hashTypeConvert(NULL, o, OBJ_ENCODING_HT);
break;
default:
/* totally unreachable */

View File

@ -886,6 +886,7 @@ struct RedisModuleDigest {
#define OBJ_ENCODING_QUICKLIST 9 /* Encoded as linked list of listpacks */
#define OBJ_ENCODING_STREAM 10 /* Encoded as a radix tree of listpacks */
#define OBJ_ENCODING_LISTPACK 11 /* Encoded as a listpack */
#define OBJ_ENCODING_LISTPACK_TTL 12 /* Encoded as a listpack with TTL metadata */
#define LRU_BITS 24
#define LRU_CLOCK_MAX ((1<<LRU_BITS)-1) /* Max value of obj->lru */
@ -2434,7 +2435,8 @@ typedef struct {
robj *subject;
int encoding;
unsigned char *fptr, *vptr;
unsigned char *fptr, *vptr, *tptr;
long long expire_time;
dictIterator *di;
dictEntry *de;
@ -3156,8 +3158,8 @@ robj *setTypeDup(robj *o);
#define HASH_SET_TAKE_VALUE (1<<1)
#define HASH_SET_COPY 0
void hashTypeConvert(robj *o, int enc);
void hashTypeTryConversion(robj *subject, robj **argv, int start, int end);
void hashTypeConvert(redisDb *db, robj *o, int enc);
void hashTypeTryConversion(redisDb *db, robj *subject, robj **argv, int start, int end);
int hashTypeExists(robj *o, sds key);
int hashTypeDelete(robj *o, sds key);
unsigned long hashTypeLength(const robj *o, int subtractExpiredFields);
@ -3168,19 +3170,24 @@ int hashTypeNext(hashTypeIterator *hi, int skipExpiredFields);
void hashTypeCurrentFromListpack(hashTypeIterator *hi, int what,
unsigned char **vstr,
unsigned int *vlen,
long long *vll);
long long *vll,
uint64_t *expireTime);
void hashTypeCurrentFromHashTable(hashTypeIterator *hi, int what, char **str,
size_t *len, uint64_t *expireTime);
void hashTypeCurrentObject(hashTypeIterator *hi, int what, unsigned char **vstr,
unsigned int *vlen, long long *vll, uint64_t *expireTime);
sds hashTypeCurrentObjectNewSds(hashTypeIterator *hi, int what);
hfield hashTypeCurrentObjectNewHfield(hashTypeIterator *hi);
hfield hashTypeCurrentObjectNewHfield(hashTypeIterator *hi, int withExpireMeta);
robj *hashTypeGetValueObject(robj *o, sds field);
int hashTypeSet(redisDb *db, robj *o, sds field, sds value, int flags);
robj *hashTypeDup(robj *o, sds newkey, uint64_t *minHashExpire);
uint64_t hashTypeRemoveFromExpires(ebuckets *hexpires, robj *o);
void hashTypeAddToExpires(redisDb *db, sds key, robj *hashObj, uint64_t expireTime);
int64_t hashTypeGetMinExpire(robj *keyObj);
uint64_t hashTypeGetNextTimeToExpire(robj *o, int accurate);
uint64_t hashTypeGetMinExpire(robj *keyObj);
void hashTypeFree(robj *o);
unsigned char *hashTypeListpackGetLp(robj *o);
int hashTypeListpackIsExpired(uint64_t expireTime);
/* Hash-Field data type (of t_hash.c) */
hfield hfieldNew(const void *field, size_t fieldlen, int withExpireMeta);

File diff suppressed because it is too large Load Diff

View File

@ -432,7 +432,7 @@ robj *setTypePopRandom(robj *set) {
if (set->encoding == OBJ_ENCODING_LISTPACK) {
/* Find random and delete it without re-seeking the listpack. */
unsigned int i = 0;
unsigned char *p = lpNextRandom(set->ptr, lpFirst(set->ptr), &i, 1, 0);
unsigned char *p = lpNextRandom(set->ptr, lpFirst(set->ptr), &i, 1, 1);
unsigned int len = 0; /* initialize to silence warning */
long long llele = 0; /* initialize to silence warning */
char *str = (char *)lpGetValue(p, &len, &llele);
@ -815,7 +815,7 @@ void spopWithCountCommand(client *c) {
unsigned int index = 0;
unsigned char **ps = zmalloc(sizeof(char *) * count);
for (unsigned long i = 0; i < count; i++) {
p = lpNextRandom(lp, p, &index, count - i, 0);
p = lpNextRandom(lp, p, &index, count - i, 1);
unsigned int len;
str = (char *)lpGetValue(p, &len, (long long *)&llele);
@ -877,7 +877,7 @@ void spopWithCountCommand(client *c) {
unsigned int index = 0;
unsigned char **ps = zmalloc(sizeof(char *) * remaining);
for (unsigned long i = 0; i < remaining; i++) {
p = lpNextRandom(lp, p, &index, remaining - i, 0);
p = lpNextRandom(lp, p, &index, remaining - i, 1);
unsigned int len;
str = (char *)lpGetValue(p, &len, (long long *)&llele);
setTypeAddAux(newset, str, len, llele, 0);
@ -1103,7 +1103,7 @@ void srandmemberWithCountCommand(client *c) {
unsigned int i = 0;
addReplyArrayLen(c, count);
while (count) {
p = lpNextRandom(lp, p, &i, count--, 0);
p = lpNextRandom(lp, p, &i, count--, 1);
unsigned int len;
str = (char *)lpGetValue(p, &len, (long long *)&llele);
if (str == NULL) {

View File

@ -1754,7 +1754,7 @@ void zsetTypeRandomElement(robj *zsetobj, unsigned long zsetsize, listpackEntry
*score = *(double*)dictGetVal(de);
} else if (zsetobj->encoding == OBJ_ENCODING_LISTPACK) {
listpackEntry val;
lpRandomPair(zsetobj->ptr, zsetsize, key, &val);
lpRandomPair(zsetobj->ptr, zsetsize, key, &val, 2);
if (score) {
if (val.sval) {
*score = zzlStrtod(val.sval,val.slen);
@ -4263,7 +4263,7 @@ void zrandmemberWithCountCommand(client *c, long l, int withscores) {
while (count) {
sample_count = count > limit ? limit : count;
count -= sample_count;
lpRandomPairs(zsetobj->ptr, sample_count, keys, vals);
lpRandomPairs(zsetobj->ptr, sample_count, keys, vals, 2);
zrandmemberReplyWithListpack(c, sample_count, keys, vals);
if (c->flags & CLIENT_CLOSE_ASAP)
break;
@ -4317,7 +4317,7 @@ void zrandmemberWithCountCommand(client *c, long l, int withscores) {
keys = zmalloc(sizeof(listpackEntry)*count);
if (withscores)
vals = zmalloc(sizeof(listpackEntry)*count);
serverAssert(lpRandomPairsUnique(zsetobj->ptr, count, keys, vals) == count);
serverAssert(lpRandomPairsUnique(zsetobj->ptr, count, keys, vals, 2) == count);
zrandmemberReplyWithListpack(c, count, keys, vals);
zfree(keys);
zfree(vals);

View File

@ -84,503 +84,546 @@ proc hrandfieldTest {activeExpireConfig} {
############################### TESTS #########################################
start_server {tags {"hash expire"}} {
foreach type {listpack ht} {
if {$type eq "ht"} {
r config set hash-max-listpack-entries 0
} else {
r config set hash-max-listpack-entries 512
}
# Currently listpack doesn't support HFE
r config set hash-max-listpack-entries 0
test "HPEXPIRE - Test 'NX' flag ($type)" {
r del myhash
r hset myhash field1 value1 field2 value2 field3 value3
assert_equal [r hpexpire myhash 1000 NX 1 field1] [list $E_OK]
assert_equal [r hpexpire myhash 1000 NX 2 field1 field2] [list $E_FAIL $E_OK]
}
test {HPEXPIRE - Test 'NX' flag} {
r del myhash
r hset myhash field1 value1 field2 value2 field3 value3
assert_equal [r hpexpire myhash 1000 NX 1 field1] [list $E_OK]
assert_equal [r hpexpire myhash 1000 NX 2 field1 field2] [list $E_FAIL $E_OK]
}
test "HPEXPIRE - Test 'XX' flag ($type)" {
r del myhash
r hset myhash field1 value1 field2 value2 field3 value3
assert_equal [r hpexpire myhash 1000 NX 2 field1 field2] [list $E_OK $E_OK]
assert_equal [r hpexpire myhash 1000 XX 2 field1 field3] [list $E_OK $E_FAIL]
}
test {HPEXPIRE - Test 'XX' flag} {
r del myhash
r hset myhash field1 value1 field2 value2 field3 value3
assert_equal [r hpexpire myhash 1000 NX 2 field1 field2] [list $E_OK $E_OK]
assert_equal [r hpexpire myhash 1000 XX 2 field1 field3] [list $E_OK $E_FAIL]
}
test "HPEXPIRE - Test 'GT' flag ($type)" {
r del myhash
r hset myhash field1 value1 field2 value2
assert_equal [r hpexpire myhash 1000 NX 1 field1] [list $E_OK]
assert_equal [r hpexpire myhash 2000 NX 1 field2] [list $E_OK]
assert_equal [r hpexpire myhash 1500 GT 2 field1 field2] [list $E_OK $E_FAIL]
}
test {HPEXPIRE - Test 'GT' flag} {
r del myhash
r hset myhash field1 value1 field2 value2
assert_equal [r hpexpire myhash 1000 NX 1 field1] [list $E_OK]
assert_equal [r hpexpire myhash 2000 NX 1 field2] [list $E_OK]
assert_equal [r hpexpire myhash 1500 GT 2 field1 field2] [list $E_OK $E_FAIL]
}
test "HPEXPIRE - Test 'LT' flag ($type)" {
r del myhash
r hset myhash field1 value1 field2 value2
assert_equal [r hpexpire myhash 1000 NX 1 field1] [list $E_OK]
assert_equal [r hpexpire myhash 2000 NX 1 field2] [list $E_OK]
assert_equal [r hpexpire myhash 1500 LT 2 field1 field2] [list $E_FAIL $E_OK]
}
test {HPEXPIRE - Test 'LT' flag} {
r del myhash
r hset myhash field1 value1 field2 value2
assert_equal [r hpexpire myhash 1000 NX 1 field1] [list $E_OK]
assert_equal [r hpexpire myhash 2000 NX 1 field2] [list $E_OK]
assert_equal [r hpexpire myhash 1500 LT 2 field1 field2] [list $E_FAIL $E_OK]
}
test "HPEXPIREAT - field not exists or TTL is in the past ($type)" {
r del myhash
r hset myhash f1 v1 f2 v2 f4 v4
r hexpire myhash 1000 NX 1 f4
assert_equal [r hexpireat myhash [expr {[clock seconds] - 1}] NX 4 f1 f2 f3 f4] "$E_DELETED $E_DELETED $E_NO_FIELD $E_FAIL"
assert_equal [r hexists myhash field1] 0
}
test {HPEXPIREAT - field not exists or TTL is in the past} {
r del myhash
r hset myhash f1 v1 f2 v2 f4 v4
r hexpire myhash 1000 NX 1 f4
assert_equal [r hexpireat myhash [expr {[clock seconds] - 1}] NX 4 f1 f2 f3 f4] "$E_DELETED $E_DELETED $E_NO_FIELD $E_FAIL"
assert_equal [r hexists myhash field1] 0
}
test "HPEXPIRE - wrong number of arguments ($type)" {
r del myhash
r hset myhash f1 v1
assert_error {*Parameter `numFields` should be greater than 0} {r hpexpire myhash 1000 NX 0 f1 f2 f3}
assert_error {*Parameter `numFileds` is more than number of arguments} {r hpexpire myhash 1000 NX 4 f1 f2 f3}
}
test {HPEXPIRE - wrong number of arguments} {
r del myhash
r hset myhash f1 v1
assert_error {*Parameter `numFields` should be greater than 0} {r hpexpire myhash 1000 NX 0 f1 f2 f3}
assert_error {*Parameter `numFileds` is more than number of arguments} {r hpexpire myhash 1000 NX 4 f1 f2 f3}
}
test "HPEXPIRE - parameter expire-time near limit of 2^48 ($type)" {
r del myhash
r hset myhash f1 v1
# below & above
assert_equal [r hpexpire myhash [expr (1<<48) - [clock milliseconds] - 1000 ] 1 f1] [list $E_OK]
assert_error {*invalid expire time*} {r hpexpire myhash [expr (1<<48) - [clock milliseconds] + 100 ] 1 f1}
}
test {HPEXPIRE - parameter expire-time near limit of 2^48} {
r del myhash
r hset myhash f1 v1
# below & above
assert_equal [r hpexpire myhash [expr (1<<48) - [clock milliseconds] - 1000 ] 1 f1] [list $E_OK]
assert_error {*invalid expire time*} {r hpexpire myhash [expr (1<<48) - [clock milliseconds] + 100 ] 1 f1}
}
test "Active/Lazy - deletes hash that all its fields got expired ($type)" {
for {set isActiveExp 0} {$isActiveExp <= 1} {incr isActiveExp} {
r debug set-active-expire $isActiveExp
r flushall
test {Active/Lazy - deletes hash that all its fields got expired} {
for {set isActiveExp 0} {$isActiveExp <= 1} {incr isActiveExp} {
r debug set-active-expire $isActiveExp
set hash_sizes {1 15 16 17 31 32 33 40}
foreach h $hash_sizes {
for {set i 1} {$i <= $h} {incr i} {
# random expiration time
r hset hrand$h f$i v$i
r hpexpire hrand$h [expr {50 + int(rand() * 50)}] 1 f$i
assert_equal 1 [r HEXISTS hrand$h f$i]
# same expiration time
r hset same$h f$i v$i
r hpexpire same$h 100 1 f$i
assert_equal 1 [r HEXISTS same$h f$i]
# same expiration time
r hset mix$h f$i v$i fieldWithoutExpire$i v$i
r hpexpire mix$h 100 1 f$i
assert_equal 1 [r HEXISTS mix$h f$i]
}
}
after 150
# Verify that all fields got expired and keys got deleted
foreach h $hash_sizes {
for {set i 1} {$i <= $h} {incr i} {
assert_equal 0 [r HEXISTS mix$h f$i]
}
assert_equal 0 [r EXISTS hrand$h]
assert_equal 0 [r EXISTS same$h]
assert_equal $h [r HLEN mix$h]
}
}
}
test "HPEXPIRE - Flushall deletes all pending expired fields ($type)" {
r del myhash
r hset myhash field1 value1 field2 value2
r hpexpire myhash 10000 NX 1 field1
r hpexpire myhash 10000 NX 1 field2
r flushall
r del myhash
r hset myhash field1 value1 field2 value2
r hpexpire myhash 10000 NX 1 field1
r hpexpire myhash 10000 NX 1 field2
r flushall async
}
set hash_sizes {1 15 16 17 31 32 33 40}
foreach h $hash_sizes {
for {set i 1} {$i <= $h} {incr i} {
test "HTTL/HPTTL - Input validation gets failed on nonexists field or field without expire ($type)" {
r del myhash
r HSET myhash field1 value1 field2 value2
r HPEXPIRE myhash 1000 NX 1 field1
# random expiration time
r hset hrand$h f$i v$i
r hpexpire hrand$h [expr {50 + int(rand() * 50)}] 1 f$i
assert_equal 1 [r HEXISTS hrand$h f$i]
# same expiration time
r hset same$h f$i v$i
r hpexpire same$h 100 1 f$i
assert_equal 1 [r HEXISTS same$h f$i]
# same expiration time
r hset mix$h f$i v$i fieldWithoutExpire$i v$i
r hpexpire mix$h 100 1 f$i
assert_equal 1 [r HEXISTS mix$h f$i]
}
}
after 150
# Verify that all fields got expired and keys got deleted
foreach h $hash_sizes {
for {set i 1} {$i <= $h} {incr i} {
assert_equal 0 [r HEXISTS mix$h f$i]
}
assert_equal 0 [r EXISTS hrand$h]
assert_equal 0 [r EXISTS same$h]
assert_equal $h [r HLEN mix$h]
foreach cmd {HTTL HPTTL} {
assert_equal [r $cmd non_exists_key 1 f] {}
assert_equal [r $cmd myhash 2 field2 non_exists_field] "$T_NO_EXPIRY $T_NO_FIELD"
# Set numFields less than actual number of fields. Fine.
assert_equal [r $cmd myhash 1 non_exists_field1 non_exists_field2] "$T_NO_FIELD"
}
}
}
test {HPEXPIRE - Flushall deletes all pending expired fields} {
r del myhash
r hset myhash field1 value1 field2 value2
r hpexpire myhash 10000 NX 1 field1
r hpexpire myhash 10000 NX 1 field2
r flushall
r del myhash
r hset myhash field1 value1 field2 value2
r hpexpire myhash 10000 NX 1 field1
r hpexpire myhash 10000 NX 1 field2
r flushall async
}
test {HTTL/HPTTL - Input validation gets failed on nonexists field or field without expire} {
r del myhash
r HSET myhash field1 value1 field2 value2
r HPEXPIRE myhash 1000 NX 1 field1
foreach cmd {HTTL HPTTL} {
assert_equal [r $cmd non_exists_key 1 f] {}
assert_equal [r $cmd myhash 2 field2 non_exists_field] "$T_NO_EXPIRY $T_NO_FIELD"
# Set numFields less than actual number of fields. Fine.
assert_equal [r $cmd myhash 1 non_exists_field1 non_exists_field2] "$T_NO_FIELD"
test "HTTL/HPTTL - returns time to live in seconds/msillisec ($type)" {
r del myhash
r HSET myhash field1 value1 field2 value2
r HPEXPIRE myhash 2000 NX 2 field1 field2
set ttlArray [r HTTL myhash 2 field1 field2]
assert_range [lindex $ttlArray 0] 1 2
set ttl [r HPTTL myhash 1 field1]
assert_range $ttl 1000 2000
}
}
test {HTTL/HPTTL - returns time to live in seconds/msillisec} {
r del myhash
r HSET myhash field1 value1 field2 value2
r HPEXPIRE myhash 2000 NX 2 field1 field2
set ttlArray [r HTTL myhash 2 field1 field2]
assert_range [lindex $ttlArray 0] 1 2
set ttl [r HPTTL myhash 1 field1]
assert_range $ttl 1000 2000
}
test "HEXPIRETIME - returns TTL in Unix timestamp ($type)" {
r del myhash
r HSET myhash field1 value1
r HPEXPIRE myhash 1000 NX 1 field1
test {HEXPIRETIME - returns TTL in Unix timestamp} {
r del myhash
r HSET myhash field1 value1
r HPEXPIRE myhash 1000 NX 1 field1
set lo [expr {[clock seconds] + 1}]
set hi [expr {[clock seconds] + 2}]
assert_range [r HEXPIRETIME myhash 1 field1] $lo $hi
assert_range [r HPEXPIRETIME myhash 1 field1] [expr $lo*1000] [expr $hi*1000]
}
test {HTTL/HPTTL - Verify TTL progress until expiration} {
r del myhash
r hset myhash field1 value1 field2 value2
r hpexpire myhash 200 NX 1 field1
assert_range [r HPTTL myhash 1 field1] 100 200
assert_range [r HTTL myhash 1 field1] 0 1
after 100
assert_range [r HPTTL myhash 1 field1] 1 101
after 110
assert_equal [r HPTTL myhash 1 field1] $T_NO_FIELD
assert_equal [r HTTL myhash 1 field1] $T_NO_FIELD
}
test {HPEXPIRE - DEL hash with non expired fields (valgrind test)} {
r del myhash
r hset myhash field1 value1 field2 value2
r hpexpire myhash 10000 NX 1 field1
r del myhash
}
test {HEXPIREAT - Set time in the past} {
r del myhash
r hset myhash field1 value1
assert_equal [r hexpireat myhash [expr {[clock seconds] - 1}] NX 1 field1] $E_DELETED
assert_equal [r hexists myhash field1] 0
}
test {HEXPIREAT - Set time and then get TTL} {
r del myhash
r hset myhash field1 value1
r hexpireat myhash [expr {[clock seconds] + 2}] NX 1 field1
assert_range [r hpttl myhash 1 field1] 1000 2000
assert_range [r httl myhash 1 field1] 1 2
r hexpireat myhash [expr {[clock seconds] + 5}] XX 1 field1
assert_range [r httl myhash 1 field1] 4 5
}
test {Lazy expire - delete hash with expired fields} {
r del myhash
r debug set-active-expire 0
r hset myhash k v
r hpexpire myhash 1 NX 1 k
after 5
r del myhash
r debug set-active-expire 1
}
# OPEN: To decide if to delete expired fields at start of HRANDFIELD.
# test {Test HRANDFIELD does not return expired fields} {
# hrandfieldTest 0
# hrandfieldTest 1
# }
test {Test HRANDFIELD can return expired fields} {
r debug set-active-expire 0
r del myhash
r hset myhash f1 v1 f2 v2 f3 v3 f4 v4 f5 v5
r hpexpire myhash 1 NX 4 f1 f2 f3 f4
after 5
set res [cmp_hrandfield_result myhash "f1 f2 f3 f4 f5"]
assert {$res == 1}
r debug set-active-expire 1
}
test {Lazy expire - HLEN does not count expired fields} {
# Enforce only lazy expire
r debug set-active-expire 0
r del h1 h4 h18 h20
r hset h1 k1 v1
r hpexpire h1 1 NX 1 k1
r hset h4 k1 v1 k2 v2 k3 v3 k4 v4
r hpexpire h4 1 NX 3 k1 k3 k4
# beyond 16 fields: HFE DS (ebuckets) converts from list to rax
r hset h18 k1 v1 k2 v2 k3 v3 k4 v4 k5 v5 k6 v6 k7 v7 k8 v8 k9 v9 k10 v10 k11 v11 k12 v12 k13 v13 k14 v14 k15 v15 k16 v16 k17 v17 k18 v18
r hpexpire h18 1 NX 18 k1 k2 k3 k4 k5 k6 k7 k8 k9 k10 k11 k12 k13 k14 k15 k16 k17 k18
r hset h20 k1 v1 k2 v2 k3 v3 k4 v4 k5 v5 k6 v6 k7 v7 k8 v8 k9 v9 k10 v10 k11 v11 k12 v12 k13 v13 k14 v14 k15 v15 k16 v16 k17 v17 k18 v18 k19 v19 k20 v20
r hpexpire h20 1 NX 2 k1 k2
after 10
assert_equal [r hlen h1] 0
assert_equal [r hlen h4] 1
assert_equal [r hlen h18] 0
assert_equal [r hlen h20] 18
# Restore to support active expire
r debug set-active-expire 1
}
test {Lazy expire - SCAN does not report expired fields} {
# Enforce only lazy expire
r debug set-active-expire 0
r del h1 h20 h4 h18 h20
r hset h1 01 01
r hpexpire h1 1 NX 1 01
r hset h4 01 01 02 02 03 03 04 04
r hpexpire h4 1 NX 3 01 03 04
# beyond 16 fields hash-field expiration DS (ebuckets) converts from list to rax
r hset h18 01 01 02 02 03 03 04 04 05 05 06 06 07 07 08 08 09 09 10 10 11 11 12 12 13 13 14 14 15 15 16 16 17 17 18 18
r hpexpire h18 1 NX 18 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18
r hset h20 01 01 02 02 03 03 04 04 05 05 06 06 07 07 08 08 09 09 10 10 11 11 12 12 13 13 14 14 15 15 16 16 17 17 18 18 19 19 20 20
r hpexpire h20 1 NX 2 01 02
after 10
# Verify SCAN does not report expired fields
assert_equal [lsort -unique [lindex [r hscan h1 0 COUNT 10] 1]] ""
assert_equal [lsort -unique [lindex [r hscan h4 0 COUNT 10] 1]] "02"
assert_equal [lsort -unique [lindex [r hscan h18 0 COUNT 10] 1]] ""
assert_equal [lsort -unique [lindex [r hscan h20 0 COUNT 100] 1]] "03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20"
# Restore to support active expire
r debug set-active-expire 1
}
test {Test HSCAN with mostly expired fields return empty result} {
r debug set-active-expire 0
# Create hash with 1000 fields and 999 of them will be expired
r del myhash
for {set i 1} {$i <= 1000} {incr i} {
r hset myhash field$i value$i
if {$i > 1} {
r hpexpire myhash 1 NX 1 field$i
}
set lo [expr {[clock seconds] + 1}]
set hi [expr {[clock seconds] + 2}]
assert_range [r HEXPIRETIME myhash 1 field1] $lo $hi
assert_range [r HPEXPIRETIME myhash 1 field1] [expr $lo*1000] [expr $hi*1000]
}
after 3
# Verify iterative HSCAN returns either empty result or only the first field
set countEmptyResult 0
set cur 0
while 1 {
set res [r hscan myhash $cur]
set cur [lindex $res 0]
# if the result is not empty, it should contain only the first field
if {[llength [lindex $res 1]] > 0} {
assert_equal [lindex $res 1] "field1 value1"
} else {
incr countEmptyResult
}
if {$cur == 0} break
test "HTTL/HPTTL - Verify TTL progress until expiration ($type)" {
r del myhash
r hset myhash field1 value1 field2 value2
r hpexpire myhash 200 NX 1 field1
assert_range [r HPTTL myhash 1 field1] 100 200
assert_range [r HTTL myhash 1 field1] 0 1
after 100
assert_range [r HPTTL myhash 1 field1] 1 101
after 110
assert_equal [r HPTTL myhash 1 field1] $T_NO_FIELD
assert_equal [r HTTL myhash 1 field1] $T_NO_FIELD
}
assert {$countEmptyResult > 0}
r debug set-active-expire 1
}
test {Lazy expire - verify various HASH commands ignore expired fields} {
# Enforce only lazy expire
r debug set-active-expire 0
r del h1 h2 h3 h4 h5 h18
r hset h1 01 01
r hset h2 01 01 02 02
r hset h3 01 01 02 02 03 03
r hset h4 1 99 2 99 3 99 4 99
r hset h5 1 1 2 22 3 333 4 4444 5 55555
r hset h6 01 01 02 02 03 03 04 04 05 05 06 06
r hset h18 01 01 02 02 03 03 04 04 05 05 06 06 07 07 08 08 09 09 10 10 11 11 12 12 13 13 14 14 15 15 16 16 17 17 18 18
r hpexpire h1 100 NX 1 01
r hpexpire h2 100 NX 1 01
r hpexpire h2 100 NX 1 02
r hpexpire h3 100 NX 1 01
r hpexpire h4 100 NX 1 2
r hpexpire h5 100 NX 1 3
r hpexpire h6 100 NX 1 05
r hpexpire h18 100 NX 17 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17
test "HPEXPIRE - DEL hash with non expired fields (valgrind test) ($type)" {
r del myhash
r hset myhash field1 value1 field2 value2
r hpexpire myhash 10000 NX 1 field1
r del myhash
}
after 150
test "HEXPIREAT - Set time in the past ($type)" {
r del myhash
r hset myhash field1 value1
assert_equal [r hexpireat myhash [expr {[clock seconds] - 1}] NX 1 field1] $E_DELETED
assert_equal [r hexists myhash field1] 0
}
# Verify HDEL ignore expired field
assert_equal [r HDEL h1 01] "0"
# Verify HGET ignore expired field
assert_equal [r HGET h2 01] ""
assert_equal [r HGET h2 02] ""
assert_equal [r HGET h3 01] ""
assert_equal [r HGET h3 02] "02"
assert_equal [r HGET h3 03] "03"
# Verify HINCRBY ignore expired field
assert_equal [r HINCRBY h4 2 1] "1"
assert_equal [r HINCRBY h4 3 1] "100"
# Verify HSTRLEN ignore expired field
assert_equal [r HSTRLEN h5 3] "0"
assert_equal [r HSTRLEN h5 4] "4"
assert_equal [lsort [r HKEYS h6]] "01 02 03 04 06"
# Verify HEXISTS ignore expired field
assert_equal [r HEXISTS h18 07] "0"
assert_equal [r HEXISTS h18 18] "1"
# Verify HVALS ignore expired field
assert_equal [lsort [r HVALS h18]] "18"
# Restore to support active expire
r debug set-active-expire 1
}
test "HEXPIREAT - Set time and then get TTL ($type)" {
r del myhash
r hset myhash field1 value1
test {A field with TTL overridden with another value (TTL discarded)} {
r del myhash
r hset myhash field1 value1
r hpexpire myhash 1 NX 1 field1
r hset myhash field1 value2
after 5
# Expected TTL will be discarded
assert_equal [r hget myhash field1] "value2"
}
r hexpireat myhash [expr {[clock seconds] + 2}] NX 1 field1
assert_range [r hpttl myhash 1 field1] 1000 2000
assert_range [r httl myhash 1 field1] 1 2
test {Modify TTL of a field} {
r del myhash
r hset myhash field1 value1
r hpexpire myhash 200 NX 1 field1
r hpexpire myhash 1000 XX 1 field1
after 15
assert_equal [r hget myhash field1] "value1"
assert_range [r hpttl myhash 1 field1] 900 1000
}
r hexpireat myhash [expr {[clock seconds] + 5}] XX 1 field1
assert_range [r httl myhash 1 field1] 4 5
}
test {Test HGETALL with fields to be expired} {
r debug set-active-expire 0
r del myhash
r hset myhash f1 v1 f2 v2 f3 v3 f4 v4 f5 v5
r hpexpire myhash 1 NX 2 f2 f4
after 10
assert_equal [lsort [r hgetall myhash]] "f1 f3 f5 v1 v3 v5"
r hset myhash f6 v6 f7 v7 f8 v8 f9 v9 f10 v10 f11 v11 f12 v12 f13 v13 f14 v14 f15 v15 f16 v16 f17 v17 f18 v18 f19 v19 f20 v20
r hpexpire myhash 1 NX 2 f6 f8
after 10
assert_equal [lsort [r hgetall myhash]] [lsort "f1 f3 f5 f7 f9 f10 f11 f12 f13 f14 f15 f16 f17 f18 f19 f20 v1 v3 v5 v7 v9 v10 v11 v12 v13 v14 v15 v16 v17 v18 v19 v20"]
r debug set-active-expire 1
}
test "Lazy expire - delete hash with expired fields ($type)" {
r del myhash
r debug set-active-expire 0
r hset myhash k v
r hpexpire myhash 1 NX 1 k
after 5
r del myhash
r debug set-active-expire 1
}
test {Test RANDOMKEY not return hash if all its fields are expired} {
for {set i 0} {$i < 100} {incr i} {
# OPEN: To decide if to delete expired fields at start of HRANDFIELD.
# test "Test HRANDFIELD does not return expired fields ($type)" {
# hrandfieldTest 0
# hrandfieldTest 1
# }
test "Test HRANDFIELD can return expired fields ($type)" {
r debug set-active-expire 0
r del myhash
r hset myhash f1 v1 f2 v2 f3 v3 f4 v4 f5 v5
r hpexpire myhash 1 NX 4 f1 f2 f3 f4
after 5
set res [cmp_hrandfield_result myhash "f1 f2 f3 f4 f5"]
assert {$res == 1}
r debug set-active-expire 1
}
test "Lazy expire - HLEN does not count expired fields ($type)" {
# Enforce only lazy expire
r debug set-active-expire 0
r flushall
# Set a small number because after some unsuccessful retries to find a
# non-expired key, the command will return an expired key
for {set h 1} {$h <= 4} {incr h} {
r hset h$h f1 v1
r hpexpire h$h 1 NX 1 f1
#r pexpire h$h 1
}
# Create a single hash without any field to be expired
r hset h5 f1 v1
after 5
r del h1 h4 h18 h20
r hset h1 k1 v1
r hpexpire h1 1 NX 1 k1
assert_equal [r RANDOMKEY] "h5"
r hset h4 k1 v1 k2 v2 k3 v3 k4 v4
r hpexpire h4 1 NX 3 k1 k3 k4
# beyond 16 fields: HFE DS (ebuckets) converts from list to rax
r hset h18 k1 v1 k2 v2 k3 v3 k4 v4 k5 v5 k6 v6 k7 v7 k8 v8 k9 v9 k10 v10 k11 v11 k12 v12 k13 v13 k14 v14 k15 v15 k16 v16 k17 v17 k18 v18
r hpexpire h18 1 NX 18 k1 k2 k3 k4 k5 k6 k7 k8 k9 k10 k11 k12 k13 k14 k15 k16 k17 k18
r hset h20 k1 v1 k2 v2 k3 v3 k4 v4 k5 v5 k6 v6 k7 v7 k8 v8 k9 v9 k10 v10 k11 v11 k12 v12 k13 v13 k14 v14 k15 v15 k16 v16 k17 v17 k18 v18 k19 v19 k20 v20
r hpexpire h20 1 NX 2 k1 k2
after 10
assert_equal [r hlen h1] 0
assert_equal [r hlen h4] 1
assert_equal [r hlen h18] 0
assert_equal [r hlen h20] 18
# Restore to support active expire
r debug set-active-expire 1
}
}
test {Test RENAME hash with fields to be expired} {
r debug set-active-expire 0
r del myhash
r hset myhash field1 value1
r hpexpire myhash 20 NX 1 field1
r rename myhash myhash2
assert_equal [r exists myhash] 0
assert_range [r hpttl myhash2 1 field1] 1 20
after 25
assert_equal [r exists myhash2] 0
r debug set-active-expire 1
}
test "Lazy expire - SCAN does not report expired fields ($type)" {
# Enforce only lazy expire
r debug set-active-expire 0
test {MOVE hash with fields to be expired} {
r select 9
r flushall
r hset myhash field1 value1
r hpexpire myhash 100 NX 1 field1
r move myhash 10
assert_equal [r exists myhash] 0
assert_equal [r dbsize] 0
r select 10
assert_equal [r hget myhash field1] "value1"
assert_equal [r dbsize] 1
after 120
assert_equal [r hget myhash field1] ""
assert_equal [r dbsize] 0
} {} {singledb:skip}
r del h1 h20 h4 h18 h20
r hset h1 01 01
r hpexpire h1 1 NX 1 01
test {Test COPY hash with fields to be expired} {
r flushall
r hset h1 f1 v1 f2 v2
r hset h2 f1 v1 f2 v2 f3 v3 f4 v4 f5 v5 f6 v6 f7 v7 f8 v8 f9 v9 f10 v10 f11 v11 f12 v12 f13 v13 f14 v14 f15 v15 f16 v16 f17 v17 f18 v18
r hpexpire h1 100 NX 1 f1
r hpexpire h2 100 NX 18 f1 f2 f3 f4 f5 f6 f7 f8 f9 f10 f11 f12 f13 f14 f15 f16 f17 f18
r COPY h1 c1
r COPY h2 c2
assert_equal [r hget h1 f1] "v1"
assert_equal [r hget c1 f1] "v1"
assert_equal [r exists h2] 1
assert_equal [r exists c2] 1
after 105
assert_equal [r hget h1 f1] ""
assert_equal [r hget c1 f1] ""
assert_equal [r exists h2] 0
assert_equal [r exists c2] 0
r hset h4 01 01 02 02 03 03 04 04
r hpexpire h4 1 NX 3 01 03 04
} {} {singledb:skip}
# beyond 16 fields hash-field expiration DS (ebuckets) converts from list to rax
test {Test SWAPDB hash-fields to be expired} {
r select 9
r flushall
r hset myhash field1 value1
r hpexpire myhash 100 NX 1 field1
r swapdb 9 10
assert_equal [r exists myhash] 0
assert_equal [r dbsize] 0
r select 10
assert_equal [r hget myhash field1] "value1"
assert_equal [r dbsize] 1
r hset h18 01 01 02 02 03 03 04 04 05 05 06 06 07 07 08 08 09 09 10 10 11 11 12 12 13 13 14 14 15 15 16 16 17 17 18 18
r hpexpire h18 1 NX 18 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18
after 100
r hset h20 01 01 02 02 03 03 04 04 05 05 06 06 07 07 08 08 09 09 10 10 11 11 12 12 13 13 14 14 15 15 16 16 17 17 18 18 19 19 20 20
r hpexpire h20 1 NX 2 01 02
wait_for_condition 20 10 {
[r hget myhash field1] == ""
} else {
fail "Field 'field1' should be expired"
after 10
# Verify SCAN does not report expired fields
assert_equal [lsort -unique [lindex [r hscan h1 0 COUNT 10] 1]] ""
assert_equal [lsort -unique [lindex [r hscan h4 0 COUNT 10] 1]] "02"
assert_equal [lsort -unique [lindex [r hscan h18 0 COUNT 10] 1]] ""
assert_equal [lsort -unique [lindex [r hscan h20 0 COUNT 100] 1]] "03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20"
# Restore to support active expire
r debug set-active-expire 1
}
assert_equal [r exists myhash] 0
} {} {singledb:skip}
test "Test HSCAN with mostly expired fields return empty result ($type)" {
r debug set-active-expire 0
test {HPERSIST - input validation} {
# HPERSIST key <num-fields> <field [field ...]>
r del myhash
r hset myhash f1 v1 f2 v2
r hexpire myhash 1000 NX 1 f1
assert_error {*wrong number of arguments*} {r hpersist myhash}
assert_error {*wrong number of arguments*} {r hpersist myhash 1}
assert_equal [r hpersist not-exists-key 1 f1] {}
assert_equal [r hpersist myhash 2 f1 not-exists-field] "$P_OK $P_NO_FIELD"
assert_equal [r hpersist myhash 1 f2] "$P_NO_EXPIRY"
}
# Create hash with 1000 fields and 999 of them will be expired
r del myhash
for {set i 1} {$i <= 1000} {incr i} {
r hset myhash field$i value$i
if {$i > 1} {
r hpexpire myhash 1 NX 1 field$i
}
}
after 3
test {HPERSIST - verify fields with TTL are persisted} {
r del myhash
r hset myhash f1 v1 f2 v2
r hexpire myhash 20 NX 2 f1 f2
r hpersist myhash 2 f1 f2
after 25
assert_equal [r hget myhash f1] "v1"
assert_equal [r hget myhash f2] "v2"
assert_equal [r HTTL myhash 2 f1 f2] "$T_NO_EXPIRY $T_NO_EXPIRY"
# Verify iterative HSCAN returns either empty result or only the first field
set countEmptyResult 0
set cur 0
while 1 {
set res [r hscan myhash $cur]
set cur [lindex $res 0]
# if the result is not empty, it should contain only the first field
if {[llength [lindex $res 1]] > 0} {
assert_equal [lindex $res 1] "field1 value1"
} else {
incr countEmptyResult
}
if {$cur == 0} break
}
assert {$countEmptyResult > 0}
r debug set-active-expire 1
}
test "Lazy expire - verify various HASH commands ignore expired fields ($type)" {
# Enforce only lazy expire
r debug set-active-expire 0
r del h1 h2 h3 h4 h5 h18
r hset h1 01 01
r hset h2 01 01 02 02
r hset h3 01 01 02 02 03 03
r hset h4 1 99 2 99 3 99 4 99
r hset h5 1 1 2 22 3 333 4 4444 5 55555
r hset h6 01 01 02 02 03 03 04 04 05 05 06 06
r hset h18 01 01 02 02 03 03 04 04 05 05 06 06 07 07 08 08 09 09 10 10 11 11 12 12 13 13 14 14 15 15 16 16 17 17 18 18
r hpexpire h1 100 NX 1 01
r hpexpire h2 100 NX 1 01
r hpexpire h2 100 NX 1 02
r hpexpire h3 100 NX 1 01
r hpexpire h4 100 NX 1 2
r hpexpire h5 100 NX 1 3
r hpexpire h6 100 NX 1 05
r hpexpire h18 100 NX 17 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17
after 150
# Verify HDEL ignore expired field
assert_equal [r HDEL h1 01] "0"
# Verify HGET ignore expired field
assert_equal [r HGET h2 01] ""
assert_equal [r HGET h2 02] ""
assert_equal [r HGET h3 01] ""
assert_equal [r HGET h3 02] "02"
assert_equal [r HGET h3 03] "03"
# Verify HINCRBY ignore expired field
assert_equal [r HINCRBY h4 2 1] "1"
assert_equal [r HINCRBY h4 3 1] "100"
# Verify HSTRLEN ignore expired field
assert_equal [r HSTRLEN h5 3] "0"
assert_equal [r HSTRLEN h5 4] "4"
assert_equal [lsort [r HKEYS h6]] "01 02 03 04 06"
# Verify HEXISTS ignore expired field
assert_equal [r HEXISTS h18 07] "0"
assert_equal [r HEXISTS h18 18] "1"
# Verify HVALS ignore expired field
assert_equal [lsort [r HVALS h18]] "18"
# Restore to support active expire
r debug set-active-expire 1
}
test "A field with TTL overridden with another value (TTL discarded) ($type)" {
r del myhash
r hset myhash field1 value1 field2 value2 field3 value3
r hpexpire myhash 10000 NX 1 field1
r hpexpire myhash 1 NX 1 field2
# field2 TTL will be discarded
r hset myhash field2 value4
after 5
# Expected TTL will be discarded
assert_equal [r hget myhash field2] "value4"
assert_equal [r httl myhash 2 field2 field3] "$T_NO_EXPIRY $T_NO_EXPIRY"
assert_not_equal [r httl myhash 1 field1] "$T_NO_EXPIRY"
}
test "Modify TTL of a field ($type)" {
r del myhash
r hset myhash field1 value1
r hpexpire myhash 200 NX 1 field1
r hpexpire myhash 1000 XX 1 field1
after 15
assert_equal [r hget myhash field1] "value1"
assert_range [r hpttl myhash 1 field1] 900 1000
}
test "Test HGETALL with fields to be expired ($type)" {
r debug set-active-expire 0
r del myhash
r hset myhash f1 v1 f2 v2 f3 v3 f4 v4 f5 v5
r hpexpire myhash 1 NX 2 f2 f4
after 10
assert_equal [lsort [r hgetall myhash]] "f1 f3 f5 v1 v3 v5"
r hset myhash f6 v6 f7 v7 f8 v8 f9 v9 f10 v10 f11 v11 f12 v12 f13 v13 f14 v14 f15 v15 f16 v16 f17 v17 f18 v18 f19 v19 f20 v20
r hpexpire myhash 1 NX 2 f6 f8
after 10
assert_equal [lsort [r hgetall myhash]] [lsort "f1 f3 f5 f7 f9 f10 f11 f12 f13 f14 f15 f16 f17 f18 f19 f20 v1 v3 v5 v7 v9 v10 v11 v12 v13 v14 v15 v16 v17 v18 v19 v20"]
r debug set-active-expire 1
}
test "Test RANDOMKEY not return hash if all its fields are expired ($type)" {
for {set i 0} {$i < 100} {incr i} {
# Enforce only lazy expire
r debug set-active-expire 0
r flushall
# Set a small number because after some unsuccessful retries to find a
# non-expired key, the command will return an expired key
for {set h 1} {$h <= 4} {incr h} {
r hset h$h f1 v1
r hpexpire h$h 1 NX 1 f1
#r pexpire h$h 1
}
# Create a single hash without any field to be expired
r hset h5 f1 v1
after 5
assert_equal [r RANDOMKEY] "h5"
# Restore to support active expire
r debug set-active-expire 1
}
}
test "Test RENAME hash with fields to be expired ($type)" {
r debug set-active-expire 0
r del myhash
r hset myhash field1 value1
r hpexpire myhash 20 NX 1 field1
r rename myhash myhash2
assert_equal [r exists myhash] 0
assert_range [r hpttl myhash2 1 field1] 1 20
after 25
assert_equal [r exists myhash2] 0
r debug set-active-expire 1
}
test "MOVE hash with fields to be expired ($type)" {
r select 9
r flushall
r hset myhash field1 value1
r hpexpire myhash 100 NX 1 field1
r move myhash 10
assert_equal [r exists myhash] 0
assert_equal [r dbsize] 0
r select 10
assert_equal [r hget myhash field1] "value1"
assert_equal [r dbsize] 1
after 120
assert_equal [r hget myhash field1] ""
assert_equal [r dbsize] 0
} {} {singledb:skip}
test "Test COPY hash with fields to be expired ($type)" {
r flushall
r hset h1 f1 v1 f2 v2
r hset h2 f1 v1 f2 v2 f3 v3 f4 v4 f5 v5 f6 v6 f7 v7 f8 v8 f9 v9 f10 v10 f11 v11 f12 v12 f13 v13 f14 v14 f15 v15 f16 v16 f17 v17 f18 v18
r hpexpire h1 100 NX 1 f1
r hpexpire h2 100 NX 18 f1 f2 f3 f4 f5 f6 f7 f8 f9 f10 f11 f12 f13 f14 f15 f16 f17 f18
r COPY h1 c1
r COPY h2 c2
assert_equal [r hget h1 f1] "v1"
assert_equal [r hget c1 f1] "v1"
assert_equal [r exists h2] 1
assert_equal [r exists c2] 1
after 105
assert_equal [r hget h1 f1] ""
assert_equal [r hget c1 f1] ""
assert_equal [r exists h2] 0
assert_equal [r exists c2] 0
} {} {singledb:skip}
test "Test SWAPDB hash-fields to be expired ($type)" {
r select 9
r flushall
r hset myhash field1 value1
r hpexpire myhash 100 NX 1 field1
r swapdb 9 10
assert_equal [r exists myhash] 0
assert_equal [r dbsize] 0
r select 10
assert_equal [r hget myhash field1] "value1"
assert_equal [r dbsize] 1
after 100
wait_for_condition 20 10 {
[r hget myhash field1] == ""
} else {
fail "Field 'field1' should be expired"
}
assert_equal [r exists myhash] 0
} {} {singledb:skip}
test "HPERSIST - input validation ($type)" {
# HPERSIST key <num-fields> <field [field ...]>
r del myhash
r hset myhash f1 v1 f2 v2
r hexpire myhash 1000 NX 1 f1
assert_error {*wrong number of arguments*} {r hpersist myhash}
assert_error {*wrong number of arguments*} {r hpersist myhash 1}
assert_equal [r hpersist not-exists-key 1 f1] {}
assert_equal [r hpersist myhash 2 f1 not-exists-field] "$P_OK $P_NO_FIELD"
assert_equal [r hpersist myhash 1 f2] "$P_NO_EXPIRY"
}
test "HPERSIST - verify fields with TTL are persisted ($type)" {
r del myhash
r hset myhash f1 v1 f2 v2
r hexpire myhash 20 NX 2 f1 f2
r hpersist myhash 2 f1 f2
after 25
assert_equal [r hget myhash f1] "v1"
assert_equal [r hget myhash f2] "v2"
assert_equal [r HTTL myhash 2 f1 f2] "$T_NO_EXPIRY $T_NO_EXPIRY"
}
test "HTTL/HPERSIST - Test expiry commands with non-volatile hash ($type)" {
r del myhash
r hset myhash field1 value1 field2 value2 field3 value3
assert_equal [r httl myhash 1 field1] $T_NO_EXPIRY
assert_equal [r httl myhash 1 fieldnonexist] $E_NO_FIELD
assert_equal [r hpersist myhash 1 field1] $P_NO_EXPIRY
assert_equal [r hpersist myhash 1 fieldnonexist] $P_NO_FIELD
}
}
}
start_server {tags {"hash expire"}} {
# Tests that only applies to listpack
test "Test listpack converts to ht and expiry works" {
set prev [lindex [r config get hash-max-listpack-entries] 1]
r config set hash-max-listpack-entries 10
r debug set-active-expire 0
r hset myhash f1 v1 f2 v2 f3 v3 f4 v4 f5 v5
r hpexpire myhash 5 2 f2 f4
for {set i 6} {$i < 11} {incr i} {
r hset myhash f$i v$i
}
r debug set-active-expire 1
after 50
assert_equal [lsort [r hgetall myhash]] [lsort "f1 f3 f5 f6 f7 f8 f9 f10 v1 v3 v5 v6 v7 v8 v9 v10"]
r config set hash-max-listpack-entries $prev
}
}